From 3c057b501f277a46fc6d798eddae936ff13685bc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:44:46 -0500 Subject: [PATCH 01/49] Lay out the native compiler's functions by the code each kind of run executes A warm rebuild ran about 3.5 MB of the native compiler's code, but the code was spread over its 69 MB text, so 64 KB fault-around mapped about 20 MB of it. The emitter can now take a function layout: a list of functions in tiers. It writes the listed definitions, generated helpers included, first and by tier, each in a `layout-` partition group. The LLVM helper compiles each partition's definitions in text order, so the listed ones come first in their partitions, and a helper that places functions by group keeps a tier's definitions in the same partitions. The rest keep their order and their source-file groups. - compile() takes a `functionLayout` file (` ` lines, gzip-compressed when the name ends in .gz). Keys leave out module numbers. - build-native-cli.mts uses the committed packages/compiler/native/cli.layout.gz, or the file SCRIPTC_NATIVE_LAYOUT names; an empty value and instrumented builds use none. - scripts/refresh-cli-layout.mjs builds an instrumented compiler, builds the runtime workloads and a corpus slice cold and then unchanged, and reads the functions each run executed from the raw profiles. Tier 1 is `--version` and rebuilds that hit both caches; tier 2 is the other builds. - The release workflow's cli-profile job also collects a layout on the release commit, and the CLI builds use it. Names of closures and generated helpers change with nearby edits, so a committed layout matches fewer functions as main moves on. - Forked lowering and emission workers reset the profile counters after fork and write their profile before _exit, so instrumented builds also count the work done in workers. This applies to refresh-cli-profile.mjs too. Executables the compiler produces are unchanged. --- .github/workflows/release.yml | 29 +- RELEASING.md | 2 + packages/compiler/native/cli.layout.gz | Bin 0 -> 216519 bytes packages/compiler/native/host.c | 25 +- .../compiler/src/backend/llvm/emit-module.ts | 89 +++++-- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../src/backend/llvm/function-layout.ts | 41 +++ packages/compiler/src/compile-types.ts | 5 + packages/compiler/src/index.ts | 18 ++ .../compiler/test/function-layout.test.ts | 147 +++++++++++ scripts/build-native-cli.mts | 10 + scripts/refresh-cli-layout.mjs | 248 ++++++++++++++++++ 12 files changed, 591 insertions(+), 26 deletions(-) create mode 100644 packages/compiler/native/cli.layout.gz create mode 100644 packages/compiler/src/backend/llvm/function-layout.ts create mode 100644 packages/compiler/test/function-layout.test.ts create mode 100644 scripts/refresh-cli-layout.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0ad..ef0b764ac0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,11 +100,11 @@ jobs: if (receiptRun) appendFileSync(process.env.GITHUB_OUTPUT, `receipt_run=${receiptRun}\n`); NODE - # Collects the native compiler's profile on this commit - # (scripts/refresh-cli-profile.mjs), so every platform's CLI build optimizes - # with a profile of the code it compiles. The committed profile falls behind - # main within a day or two. If this job fails, the CLI builds use the - # committed profile. + # Collects the native compiler's profile and function layout on this commit + # (scripts/refresh-cli-profile.mjs, scripts/refresh-cli-layout.mjs), so every + # platform's CLI build optimizes and lays out the code it compiles. The + # committed ones fall behind main within a day or two. If this job fails, + # the CLI builds use the committed profile and layout. cli-profile: name: Collect the native compiler profile needs: check-release @@ -138,11 +138,17 @@ jobs: - name: Collect the profile run: >- node scripts/refresh-cli-profile.mjs --tsc-ts "$RUNNER_TEMP/tsc-ts" --jobs 4 - --output "$RUNNER_TEMP/cli-profile/cli.profdata.gz" + --output "$RUNNER_TEMP/cli-profile/cli.profdata.gz" --work "$RUNNER_TEMP/cli-profile-work" + - name: Collect the function layout + run: >- + node scripts/refresh-cli-layout.mjs --jobs 4 --instrumented "$RUNNER_TEMP/cli-profile-work/cli" + --output "$RUNNER_TEMP/cli-profile/cli.layout.gz" - uses: actions/upload-artifact@v4 with: name: cli-profile - path: ${{ runner.temp }}/cli-profile/cli.profdata.gz + path: | + ${{ runner.temp }}/cli-profile/cli.profdata.gz + ${{ runner.temp }}/cli-profile/cli.layout.gz retention-days: 7 if-no-files-found: error @@ -300,8 +306,8 @@ jobs: with: name: cli-profile path: ${{ runner.temp }}/cli-profile - # Without the artifact, SCRIPTC_NATIVE_PROFILE_USE stays unset and - # build-native-cli.mts uses the committed profile. + # Without the artifact, SCRIPTC_NATIVE_PROFILE_USE and SCRIPTC_NATIVE_LAYOUT + # stay unset and build-native-cli.mts uses the committed profile and layout. - name: Select the native compiler profile if: matrix.cli != '' shell: bash @@ -311,6 +317,11 @@ jobs: if [ -n "$COLLECTED" ]; then echo "SCRIPTC_NATIVE_PROFILE_USE=$COLLECTED" >> "$GITHUB_ENV" echo "The native CLI builds with the profile collected on $GITHUB_SHA" + layout="$(dirname "$COLLECTED")/cli.layout.gz" + if [ -f "$layout" ]; then + echo "SCRIPTC_NATIVE_LAYOUT=$layout" >> "$GITHUB_ENV" + echo "The native CLI builds with the function layout collected on $GITHUB_SHA" + fi else echo "::warning::No profile was collected on this commit; the native CLI builds with the committed profile" fi diff --git a/RELEASING.md b/RELEASING.md index 42bfe75add..42c253568b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -18,6 +18,8 @@ The release job builds each pinned LLVM helper, precompiled runtime pack, and na Release builds of the native CLI optimize the compiler with a profile collected on the release commit. The `cli-profile` job runs `node scripts/refresh-cli-profile.mjs` on Linux x64, which builds an instrumented compiler, runs it on tsc-ts, a slice of the corpus, and the runtime benchmark workloads, and writes the merged sparse profile. Every platform's CLI build uses that profile (`SCRIPTC_NATIVE_PROFILE_USE`), and the GitHub release attaches it as `scriptc--cli.profdata.gz`. Code the profile does not describe, or describes from older compiler sources, is optimized as without a profile. When the profile job fails, the CLI builds use the committed profile `packages/compiler/native/cli.profdata.gz`, which local and CI builds of the native compiler also use. Refresh the committed profile when compiler code has changed substantially since the last refresh: run `node scripts/refresh-cli-profile.mjs --tsc-ts ` on a quiet Linux x64 host and commit the new profile on its own. +Builds of the native compiler also order its functions by the committed layout `packages/compiler/native/cli.layout.gz`: the functions that `--version` and rebuilds of unchanged programs run come first, then the ones other builds run, so a run maps fewer pages of the executable. Functions the layout does not list keep their usual order. Function names of closures and generated helpers change with nearby edits, so the `cli-profile` job also collects a layout on the release commit (`scripts/refresh-cli-layout.mjs`, reusing the job's instrumented compiler), and the CLI builds use it (`SCRIPTC_NATIVE_LAYOUT`). Refresh the committed layout the same way as the profile, with `node scripts/refresh-cli-layout.mjs`, and commit it on its own. + Publishing uses npm trusted publishing (OIDC) — there is no npm token secret. Each published package must have a GitHub Actions trusted publisher for `release.yml` and the `Release` environment. A missing configuration fails diff --git a/packages/compiler/native/cli.layout.gz b/packages/compiler/native/cli.layout.gz new file mode 100644 index 0000000000000000000000000000000000000000..ff1f8b97e93e958cd850eb6991d489661b1d678e GIT binary patch literal 216519 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 literal 0 HcmV?d00001 diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index 24d330cc0e..f4393c5f75 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -80,9 +80,30 @@ void scriptc_native_unlock(void) {} #include #include -int32_t scriptc_native_fork(void) { return (int32_t)fork(); } +#if defined(__linux__) +/* LLVM's profile runtime, linked into instrumented builds of the compiler + * (SCRIPTC_NATIVE_PROFILE_GENERATE); other builds leave these null. A worker + * starts with zeroed counters and writes its profile before _exit, which + * skips the runtime's atexit writer, so the profile counts the worker's work + * once. */ +extern void __llvm_profile_reset_counters(void) __attribute__((weak)); +extern int __llvm_profile_write_file(void) __attribute__((weak)); +#endif -void scriptc_native_exit(int32_t code) { _exit(code); } +int32_t scriptc_native_fork(void) { + pid_t pid = fork(); +#if defined(__linux__) + if (pid == 0 && __llvm_profile_reset_counters != NULL) __llvm_profile_reset_counters(); +#endif + return (int32_t)pid; +} + +void scriptc_native_exit(int32_t code) { +#if defined(__linux__) + if (__llvm_profile_write_file != NULL) (void)__llvm_profile_write_file(); +#endif + _exit(code); +} /* The worker's exit status, 128 plus the signal for a worker a signal * ended, or -1 when waitpid fails. */ diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2ce..24c32d2e93 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -66,6 +66,7 @@ import { emitLibraryIdentityLines } from "../library-identity-markers.js"; import { InternalCompilerError } from "../../errors.js"; import { targetCpuAttribute } from "../target-cpu.js"; import type { IrFunction } from "../../ir/ir.js"; +import { type FunctionLayout, layoutTierOf } from "./function-layout.js"; /** The function bodies emitted in `chunks` contiguous ranges, each by its * own emitter (LlEmitter.chunkEmitter), then merged in function order: the @@ -228,6 +229,61 @@ function partitionGroupLines(functions: readonly IrFunction[]): string[] { /** Ends the group of the definitions that follow. */ const PARTITION_GROUP_END = "; scriptc-group"; +/** Writes the generated helpers, then the function definitions with their + * group lines. With a layout, the definitions it lists, helpers included, + * move after the unlisted helpers and before the other functions, by tier, + * each in its tier's group (`layout-`). The helper compiles each + * partition's definitions in text order, so listed ones come first in their + * partitions, and it places a group's definitions in the same partitions. + * Everything else keeps its order and its source-file group. */ +function writeDefinitions( + out: string[], + layout: FunctionLayout | undefined, + helperTexts: readonly (readonly string[])[], + fnDefs: readonly string[], + groups: readonly string[], +): void { + const tierLine = (tier: number) => `${PARTITION_GROUP_END} layout-${tier}`; + const listed = new Map(); + const listIn = (tier: number, lines: readonly string[]) => { + const list = listed.get(tier) ?? []; + if (!listed.has(tier)) listed.set(tier, list); + for (const line of lines) list.push(line); + }; + if (layout === undefined || layout.size === 0) { + for (const texts of helperTexts) for (const text of texts) out.push(text); + } else { + // A helper's text can span several entries (one per line), so the + // definitions are found in the joined text. A listed definition moves + // through its closing brace; what follows it stays in place. + const joined: string[] = []; + for (const texts of helperTexts) for (const text of texts) joined.push(text); + for (const part of joined.join("\n").split(/\n(?=define )/)) { + const tier = part.startsWith("define ") ? layoutTierOf(layout, part) : undefined; + const close = tier === undefined ? -1 : part.search(/\n}(\n|$)/); + if (tier === undefined || close < 0) { + out.push(part); + continue; + } + listIn(tier, [tierLine(tier), part.slice(0, close + 2)]); + const rest = part.slice(close + 3); + if (rest !== "") out.push(rest); + } + } + const unlisted: number[] = []; + fnDefs.forEach((text, i) => { + const tier = layout === undefined || layout.size === 0 ? undefined : layoutTierOf(layout, text); + if (tier === undefined) unlisted.push(i); + else listIn(tier, ["", tierLine(tier), text]); + }); + for (const tier of [...listed.keys()].sort((a, b) => a - b)) + for (const line of listed.get(tier)!) out.push(line); + for (const [k, i] of unlisted.entries()) { + if (k > 0 || listed.size > 0) out.push(""); + out.push(groups[i] ?? PARTITION_GROUP_END, fnDefs[i]!); + } +} + export function emitModuleParts(emitter: LlEmitter): string[] { // Function bodies first (the literal/unit/fn-value tables fill as they // emit), then the file assembles around them — the runtime ABI’s order. @@ -942,21 +998,24 @@ export function emitModuleParts(emitter: LlEmitter): string[] { ); } if (globals.length > 0) out.push(``); - for (const line of helpers) out.push(line); - for (const line of ffiCallbacks.defs) out.push(line); - for (const line of shapes.defs) out.push(line); - for (const line of classShapes.defs) out.push(line); - for (const line of classObjDefs) out.push(line); - for (const line of emitter.walkers.defs) out.push(line); - for (const line of emitter.dyn.defs) out.push(line); - for (const line of wrappers) out.push(line); - for (const line of asyncDefs) out.push(line); - for (const line of emitter.resolveThunkDefs) out.push(line); - const groups = partitionGroupLines(emitter.mod.functions); - for (let i = 0; i < fnDefs.length; i++) { - if (i > 0) out.push(""); - out.push(groups[i] ?? PARTITION_GROUP_END, fnDefs[i]!); - } + writeDefinitions( + out, + emitter.targetOptions.functionLayout, + [ + helpers, + ffiCallbacks.defs, + shapes.defs, + classShapes.defs, + classObjDefs, + emitter.walkers.defs, + emitter.dyn.defs, + wrappers, + asyncDefs, + emitter.resolveThunkDefs, + ], + fnDefs, + partitionGroupLines(emitter.mod.functions), + ); if (fnDefs.length === 0) out.push(""); out.push(PARTITION_GROUP_END, ""); // WebAssembly modules translated into this program, and the registry. diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 5569599ee3..0b0bf11e09 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1,4 +1,5 @@ import type { TargetCpu } from "../target-cpu.js"; +import type { FunctionLayout } from "./function-layout.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { wasmBoundsChecks } from "./wasm-modules.js"; import { jsTruthy } from "./jsval.js"; @@ -487,6 +488,8 @@ export interface LlvmTargetOptions { /** The host's process primitives, where it can fork (the native * compiler's). */ forkPrimitives?: ForkPrimitives | undefined; + /** Functions to write first, by tier (function-layout.ts). */ + functionLayout?: FunctionLayout; /** Exact frontend sources for dev-build locations and variable metadata. */ debugSources?: ReadonlyMap; /** Pointer width of the target C ABI. Native targets are 64-bit today. */ diff --git a/packages/compiler/src/backend/llvm/function-layout.ts b/packages/compiler/src/backend/llvm/function-layout.ts new file mode 100644 index 0000000000..00cf233dbd --- /dev/null +++ b/packages/compiler/src/backend/llvm/function-layout.ts @@ -0,0 +1,41 @@ +/** Function layouts: the functions that runs of a program execute, in tiers + * (scripts/refresh-cli-layout.mjs writes the native compiler's). The emitter + * writes a listed function before the others, in a partition group of its + * tier, so the code that runs together sits together in the executable. */ + +/** Layout key → tier (1 first). */ +export type FunctionLayout = ReadonlyMap; + +/** The layout key of an LLVM symbol: module numbers (`%m12.`, `%init.12`), + * which change when modules are added, are left out. */ +export function functionLayoutKey(symbol: string): string { + return symbol.replace(/_x25_m\d+_/g, "_x25_m_").replace(/_x25_init_\d+$/, "_x25_init_"); +} + +/** Reads a layout's lines, each ` `. A function listed in two + * tiers keeps the first. */ +export function parseFunctionLayout(text: string): FunctionLayout { + const layout = new Map(); + for (const line of text.split("\n")) { + const space = line.indexOf(" "); + if (space < 1) continue; + const tier = Number(line.slice(0, space)); + const key = functionLayoutKey(line.slice(space + 1).trim()); + if (Number.isInteger(tier) && tier > 0 && !layout.has(key)) layout.set(key, tier); + } + return layout; +} + +/** The lowest tier of the functions `text` defines, or undefined when the + * layout lists none of them. */ +export function layoutTierOf(layout: FunctionLayout, text: string): number | undefined { + let tier: number | undefined; + for (let at = text.indexOf("define "); at >= 0; at = text.indexOf("\ndefine ", at + 1)) { + const name = text.indexOf("@", at); + const open = text.indexOf("(", name); + if (name < 0 || open < 0) break; + const listed = layout.get(functionLayoutKey(text.slice(name + 1, open))); + if (listed !== undefined && (tier === undefined || listed < tier)) tier = listed; + } + return tier; +} diff --git a/packages/compiler/src/compile-types.ts b/packages/compiler/src/compile-types.ts index 8ec7b5ffd1..2703e008bf 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -54,6 +54,11 @@ export interface CompileBaseOptions { * merge-profiles`). Code the profile does not describe, or describes * from an older version of the program, is optimized as without one. */ profileUse?: string; + /** A function layout (scripts/refresh-cli-layout.mjs; gzip-compressed when + * the name ends in .gz): the listed functions are written first, by tier, + * so the code that runs together sits together in the executable. The + * native compiler's build uses it. Executables and LLVM output. */ + functionLayout?: string; /** Eligible packages compile automatically. Explicit names override automatic selection heuristics. */ npmStatic?: readonly string[] | "auto"; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index ffd56a1bf7..ace268af78 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -48,6 +48,7 @@ export type { import { compilePackedLibrary } from "./backend/library-pack.js"; import { node24FetchCompatProfile } from "./compat/fetch-profile.js"; import { libFnSignatures } from "./ir/builtin-signatures.js"; +import { type FunctionLayout, parseFunctionLayout } from "./backend/llvm/function-layout.js"; import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; @@ -57,6 +58,7 @@ import { copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node: import { tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { gunzipSync } from "node:zlib"; import { clearCcCaches, configuredTargetPlatform, @@ -774,6 +776,20 @@ async function readNativeProfile( return { use: { path, digest: createHash("sha256").update(bytes).digest("hex") } }; } +/** The request's function layout. */ +async function readFunctionLayout( + opts: CompileRequestOptions, +): Promise<{ functionLayout?: FunctionLayout }> { + if (opts.functionLayout === undefined) return {}; + const path = resolve(opts.functionLayout); + const bytes = await readFile(path); + return { + functionLayout: parseFunctionLayout( + (path.endsWith(".gz") ? gunzipSync(bytes) : bytes).toString("utf8"), + ), + }; +} + function usesPrecompiledRuntimePack(opts: CompileRequestOptions, backend: "llvm"): boolean { if (backend !== "llvm" || opts.sanitize === true) return false; return nativeCodegenTarget() !== null; @@ -847,6 +863,7 @@ async function prepareExecutableInput( llvm = emitLlvmModuleSource(mod, { targetTriple: process.env["SCRIPTC_TARGET"] ?? "", ...debugOptions, + ...(await readFunctionLayout(opts)), pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: outputKind === "obj", @@ -920,6 +937,7 @@ async function prepareExecutableInput( llvmSource = emitLlvmModuleSource(mod, { targetTriple: process.env["SCRIPTC_TARGET"] ?? "", ...debugOptions, + ...(await readFunctionLayout(opts)), pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: useRuntimePack, diff --git a/packages/compiler/test/function-layout.test.ts b/packages/compiler/test/function-layout.test.ts new file mode 100644 index 0000000000..8619c5b342 --- /dev/null +++ b/packages/compiler/test/function-layout.test.ts @@ -0,0 +1,147 @@ +import { execFileSync } from "node:child_process"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { gzipSync } from "node:zlib"; +import { expect, test } from "vitest"; +import { compile } from "../src/index.js"; +import { + functionLayoutKey, + layoutTierOf, + parseFunctionLayout, +} from "../src/backend/llvm/function-layout.js"; + +const source = ` +function first(n: number): number { return n + 1; } +function second(n: number): number { return n * 2; } +function third(n: number): number { return n - 3; } +console.log(first(Number(process.argv.length)), second(4), third(5)); +`; + +async function emitLlvm(layout?: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ll"); + await writeFile(entry, source); + let functionLayout: string | undefined; + if (layout !== undefined) { + functionLayout = join(dir, "layout.gz"); + await writeFile(functionLayout, gzipSync(layout)); + } + const result = await compile(entry, { + outDir: dir, + outPath, + outputKind: "llvm", + ...(functionLayout === undefined ? {} : { functionLayout }), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + return await readFile(outPath, "utf8"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +/** Each defined function's name with the partition group line it follows. */ +function definitions(llvm: string): { name: string; group: string }[] { + const out: { name: string; group: string }[] = []; + let group = ""; + for (const line of llvm.split("\n")) { + if (line.startsWith("; scriptc-group")) group = line.slice("; scriptc-group".length).trim(); + const name = /^define [^@]*@([\w.$]+)\(/.exec(line)?.[1]; + if (name !== undefined) out.push({ name, group }); + } + return out; +} + +test("a layout writes the functions it lists first, by tier, in their tier's group", async () => { + const plain = definitions(await emitLlvm()).filter((d) => /_(first|second|third)$/.test(d.name)); + expect(plain.map((d) => d.name.replace(/^sc_b?f_/, ""))).toEqual(["first", "second", "third"]); + expect(plain.every((d) => !d.group.startsWith("layout-"))).toBe(true); + + const names = new Map(plain.map((d) => [d.name.replace(/^sc_b?f_/, ""), d.name])); + const laid = definitions(await emitLlvm(`2 ${names.get("first")}\n1 ${names.get("third")}\n`)); + const index = (fn: string) => laid.findIndex((d) => d.name === names.get(fn)); + expect(index("third")).toBeLessThan(index("first")); + expect(index("first")).toBeLessThan(index("second")); + expect(laid[index("third")]!.group).toBe("layout-1"); + expect(laid[index("first")]!.group).toBe("layout-2"); + expect(laid[index("second")]!.group).not.toMatch(/^layout-/); + // Listed functions precede every unlisted definition in the function section. + const firstUnlisted = laid.findIndex( + (d, i) => i > index("third") && !d.group.startsWith("layout-"), + ); + expect(firstUnlisted).toBeGreaterThan(index("first")); +}); + +test("layout keys leave out module numbers", () => { + expect(functionLayoutKey("sc_bf__x25_m447_lowerCall")).toBe( + functionLayoutKey("sc_bf__x25_m12_lowerCall"), + ); + expect(functionLayoutKey("sc_f__x25_init_253")).toBe(functionLayoutKey("sc_f__x25_init_7")); + expect(functionLayoutKey("sc_bf__x25_m3_Lexer_scan")).not.toBe( + functionLayoutKey("sc_bf__x25_m3_Lexer_peek"), + ); +}); + +test("a function listed twice keeps its first tier, and malformed lines are skipped", () => { + const layout = parseFunctionLayout("1 sc_f_a\n2 sc_f_a\n2 sc_f_b\nnot a line\n0 sc_f_c\n\n"); + expect([...layout]).toEqual([ + ["sc_f_a", 1], + ["sc_f_b", 2], + ]); + const text = + "define internal void @sc_f_b() {\n ret void\n}\n\ndefine void @sc_f_a() {\n ret void\n}"; + expect(layoutTierOf(layout, text)).toBe(1); + expect(layoutTierOf(layout, "define void @sc_f_z() {\n ret void\n}")).toBeUndefined(); +}); + +test("an executable built with a layout that lists helpers and functions runs unchanged", async () => { + const program = ` +class Point { constructor(public x: number, public y: number) {} } +function norm(p: Point): number { return Math.sqrt(p.x * p.x + p.y * p.y); } +const points = [new Point(3, 4), new Point(6, 8)]; +console.log(points.map(norm).join(","), points[0]!.x === 3 ? "three" : "other"); +`; + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-exe-")); + try { + const entry = join(dir, "main.ts"); + await writeFile(entry, program); + const llvmPath = join(dir, "main.ll"); + const emitted = await compile(entry, { outDir: dir, outPath: llvmPath, outputKind: "llvm" }); + expect(emitted.ok, JSON.stringify(emitted.diagnostics)).toBe(true); + // The generated helpers written before the functions, and one function, + // in two tiers. + const names = definitions(await readFile(llvmPath, "utf8")).map((d) => d.name); + const helpers = names.slice( + 0, + names.findIndex((name) => /^sc_b?f_/.test(name)), + ); + expect(helpers.length).toBeGreaterThan(0); + const norm = names.find((name) => /^sc_b?f_norm$/.test(name))!; + const layout = join(dir, "layout"); + await writeFile(layout, [`1 ${norm}`, ...helpers.map((name) => `2 ${name}`)].join("\n")); + const laid = join(dir, "laid.ll"); + const laidOut = await compile(entry, { + outDir: dir, + outPath: laid, + outputKind: "llvm", + functionLayout: layout, + }); + expect(laidOut.ok, JSON.stringify(laidOut.diagnostics)).toBe(true); + const order = definitions(await readFile(laid, "utf8")); + expect(order.filter((d) => d.group === "layout-2").map((d) => d.name)).toEqual(helpers); + expect(order.find((d) => d.name === norm)!.group).toBe("layout-1"); + const exe = join(dir, "main"); + const built = await compile(entry, { + outputKind: "exe", + outDir: dir, + outPath: exe, + functionLayout: layout, + }); + expect(built.ok, JSON.stringify(built.diagnostics)).toBe(true); + expect(execFileSync(exe, { encoding: "utf8" })).toBe("5,10 three\n"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index 4d3f00ff53..a26c502094 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -35,6 +35,15 @@ const useProfile = !sanitize && requestedProfile !== "" && (requestedProfile !== undefined || existsSync(committedProfile)); +// Builds lay out the compiler's functions with the committed layout +// (scripts/refresh-cli-layout.mjs), or the one SCRIPTC_NATIVE_LAYOUT names; +// an empty SCRIPTC_NATIVE_LAYOUT, and instrumented builds, use none. +const committedLayout = join(root, "packages/compiler/native/cli.layout.gz"); +const requestedLayout = process.env["SCRIPTC_NATIVE_LAYOUT"]; +const functionLayout = + profileGenerate !== undefined || requestedLayout === "" + ? undefined + : (requestedLayout ?? (existsSync(committedLayout) ? committedLayout : undefined)); let target = nativeCodegenTarget(); if (target === null || target.platform === "wasi") throw new Error("a supported native host is required to build the compiler"); @@ -124,6 +133,7 @@ const result = await compile(join(root, "packages/compiler/src/native/cli.ts"), sanitize, ...(profileGenerate === undefined ? {} : { profileGenerate }), ...(profileUse === undefined ? {} : { profileUse }), + ...(functionLayout === undefined ? {} : { functionLayout }), emitIr: process.env["SCRIPTC_NATIVE_EMIT_IR"] === "1", }); if (!result.ok) diff --git a/scripts/refresh-cli-layout.mjs b/scripts/refresh-cli-layout.mjs new file mode 100644 index 0000000000..d401ac62b5 --- /dev/null +++ b/scripts/refresh-cli-layout.mjs @@ -0,0 +1,248 @@ +#!/usr/bin/env node +/* Refreshes packages/compiler/native/cli.layout.gz, the function layout that + * builds of the native compiler use (scripts/build-native-cli.mts). The + * layout lists, in two tiers, the functions the compiler runs: tier 1 for + * `--version` and rebuilds of unchanged programs, tier 2 for the other + * builds. The emitter writes the listed functions first, so the code a build + * runs sits together in the compiler's executable and a run maps fewer of its + * pages. + * + * Builds an instrumented native compiler for this Linux host (or reuses one + * with --instrumented), builds each training program cold and then again + * unchanged, and reads the functions each kind of run executed from the raw + * profiles. The training programs are the runtime benchmark workloads at + * `speed` and every seventh corpus program at the default optimization. A + * refresh takes about 15 minutes on 16 cores, most of it for the instrumented + * build. A stale layout lists some functions that no longer exist and misses + * new ones, which then keep their usual order. + * + * node scripts/refresh-cli-layout.mjs [--jobs 8] [--instrumented ] + */ +import { spawn, spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { + appendFileSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; +import { cpus, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { constants, gzipSync, inflateSync } from "node:zlib"; + +const root = fileURLToPath(new URL("..", import.meta.url)); +const { values } = parseArgs({ + options: { + jobs: { type: "string", default: String(Math.max(1, Math.floor(cpus().length / 2))) }, + output: { type: "string", default: join(root, "packages/compiler/native/cli.layout.gz") }, + work: { type: "string" }, + instrumented: { type: "string" }, + help: { type: "boolean", default: false }, + }, +}); +if (values.help) { + console.log( + "Usage: node scripts/refresh-cli-layout.mjs [--jobs N] [--output ] [--work

] [--instrumented ]", + ); + process.exit(0); +} +if (process.platform !== "linux" || process.arch !== "x64") + throw new Error("the compiler layout is collected on Linux x64"); +const jobs = Number(values.jobs); +const output = resolve(values.output); +mkdirSync(dirname(output), { recursive: true }); +const work = resolve(values.work ?? mkdtempSync(join(tmpdir(), "scriptc-cli-layout-"))); +const raw = join(work, "raw"); +const builds = join(work, "builds"); +rmSync(raw, { recursive: true, force: true }); +for (const dir of [raw, builds]) mkdirSync(dir, { recursive: true }); +const log = join(work, "training.log"); + +const cli = resolve(values.instrumented ?? join(work, "cli")); +if (values.instrumented === undefined) { + console.log(`building the instrumented compiler in ${cli}`); + const built = spawnSync( + process.execPath, + [ + "--max-old-space-size=8192", + "--import", + "tsx", + join(root, "scripts/build-native-cli.mts"), + cli, + ], + { + cwd: root, + stdio: "inherit", + env: { ...process.env, SCRIPTC_NATIVE_PROFILE_GENERATE: join(raw, "build-%4m.profraw") }, + }, + ); + if (built.status !== 0) throw new Error("the instrumented compiler did not build"); +} +const scriptc = join(cli, "bin/scriptc"); +// Phase timing shows whether a rebuild hit both caches. It is on for every +// run, so the rebuild's environment matches the first build's. +const baseEnv = { ...process.env, SCRIPTC_TIMING: "1" }; +delete baseEnv["SCRIPTC_LLVM_HELPER"]; + +/** Runs the instrumented compiler, writing its profile to `options.profile`. */ +function run(args, options) { + return new Promise((done) => { + const child = spawn(scriptc, args, { + cwd: options.cwd, + stdio: ["ignore", "pipe", "pipe"], + env: { + ...baseEnv, + SCRIPTC_CACHE_DIR: options.cache, + LLVM_PROFILE_FILE: options.profile, + }, + }); + let text = ""; + child.stdout.on("data", (chunk) => (text += chunk)); + child.stderr.on("data", (chunk) => (text += chunk)); + child.on("close", (code) => { + appendFileSync(log, `$ scriptc ${args.join(" ")}\n${text}exit ${code}\n`); + done({ ok: code === 0, text }); + }); + }); +} + +async function pool(tasks) { + let next = 0; + let passed = 0; + await Promise.all( + Array.from({ length: jobs }, async () => { + while (next < tasks.length) if (await tasks[next++]()) passed++; + }), + ); + return passed; +} + +const programNumber = (name) => Number.parseInt(name, 10); +const training = [ + ...readdirSync(join(root, "benchmarks/runtime/workloads")) + .filter((name) => name.endsWith(".ts")) + .map((name) => ({ + file: join(root, "benchmarks/runtime/workloads", name), + optimization: "speed", + })), + ...readdirSync(join(root, "tests/corpus")) + .filter((name) => /^\d+-.*\.ts$/.test(name) && programNumber(name) % 7 === 0) + .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "release" })), +]; + +// Tier 1 collects each run that took the warm path from its own profile; a +// rebuild that missed a cache (it shows no `frontend-cache-hit` or +// `executable-cache-hit` phase) is left out. Tier 2 pools the first builds. +const warm = new Set(); +const cold = new Set(); +let skipped = 0; +async function warmRun(args, cache, name, cacheHits) { + const profile = join(raw, `warm-${name}.profraw`); + const { ok, text } = await run(args, { cwd: root, cache, profile }); + if (ok && cacheHits.every((phase) => text.includes(`"phase":"${phase}"`))) + for (const fn of executedFunctions(profile)) warm.add(fn); + else skipped++; + rmSync(profile, { force: true }); +} + +const started = Date.now(); +await warmRun(["--version"], join(work, "cache-version"), "version", []); +console.log(`training: ${training.length} programs, cold then unchanged, ${jobs} at a time`); +const passed = await pool( + training.map((program, index) => async () => { + const cache = join(work, `cache-${index}`); + const out = join(builds, `program-${index}`); + const args = ["build", program.file, `--optimization=${program.optimization}`, "-o", out]; + const { ok } = await run(args, { cwd: root, cache, profile: join(raw, "cold-%4m.profraw") }); + if (ok) + await warmRun(args, cache, String(index), ["frontend-cache-hit", "executable-cache-hit"]); + rmSync(cache, { recursive: true, force: true }); + rmSync(out, { force: true }); + return ok; + }), +); +console.log( + `trained in ${Math.round((Date.now() - started) / 1000)} s; ${passed} of ${training.length} programs built, ${skipped} rebuilds left out of tier 1 (see ${log})`, +); + +/** Functions a raw profile (LLVM's format version 10) records as run: the + * names whose counters are not all zero, without the file prefix of + * internal functions. */ +function executedFunctions(path) { + const buf = readFileSync(path); + const u64 = (offset) => Number(buf.readBigUInt64LE(offset)); + if (buf.readBigUInt64LE(0) !== 0xff6c70726f667281n) + throw new Error(`${path} is not a 64-bit raw profile`); + const version = Number(buf.readBigUInt64LE(8) & 0xffffffffn); + if (version !== 10) throw new Error(`${path} has raw profile version ${version}, expected 10`); + const [binaryIdsBytes, records, beforeCounters, counters, afterCounters] = [2, 3, 4, 5, 6].map( + (field) => u64(field * 8), + ); + const [bitmapBytes, afterBitmap, namesBytes] = [7, 8, 9].map((field) => u64(field * 8)); + const valueKinds = u64(15 * 8) + 1; + const recordBytes = Math.ceil((6 * 8 + 4 + 2 * valueKinds + 4) / 8) * 8; + const dataStart = 16 * 8 + binaryIdsBytes; + const countersStart = dataStart + records * recordBytes + beforeCounters; + const namesStart = countersStart + counters * 8 + afterCounters + bitmapBytes + afterBitmap; + const countersDelta = buf.readBigInt64LE(10 * 8); + const names = new Map(); + let at = namesStart; + const uleb = () => { + let value = 0; + let scale = 1; + for (;;) { + const byte = buf[at++]; + value += (byte & 0x7f) * scale; + if ((byte & 0x80) === 0) return value; + scale *= 128; + } + }; + while (at < namesStart + namesBytes) { + const plain = uleb(); + const packed = uleb(); + if (plain === 0) break; + const bytes = + packed === 0 ? buf.subarray(at, at + plain) : inflateSync(buf.subarray(at, at + packed)); + at += packed === 0 ? plain : packed; + for (const name of bytes.toString("latin1").split("\x01")) + names.set(createHash("md5").update(name, "latin1").digest().readBigUInt64LE(0), name); + } + const ran = []; + for (let index = 0; index < records; index++) { + const record = dataStart + index * recordBytes; + const counterOffset = Number( + buf.readBigInt64LE(record + 16) - (countersDelta - BigInt(index * recordBytes)), + ); + const count = buf.readUInt32LE(record + 48); + for (let c = 0; c < count; c++) { + if (buf.readBigUInt64LE(countersStart + counterOffset + c * 8) !== 0n) { + const name = names.get(buf.readBigUInt64LE(record)); + if (name !== undefined) ran.push(name.slice(name.indexOf(";") + 1)); + break; + } + } + } + return ran; +} + +for (const name of readdirSync(raw)) + if (name.startsWith("cold-")) for (const fn of executedFunctions(join(raw, name))) cold.add(fn); +const lines = [ + ...[...warm].sort().map((name) => `1 ${name}`), + ...[...cold] + .filter((name) => !warm.has(name)) + .sort() + .map((name) => `2 ${name}`), +]; +writeFileSync(output, gzipSync(`${lines.join("\n")}\n`, { level: constants.Z_BEST_COMPRESSION })); +console.log( + `wrote ${output}: ${warm.size} tier-1 and ${lines.length - warm.size} tier-2 functions (${statSync(output).size} bytes)`, +); +rmSync(builds, { recursive: true, force: true }); +if (values.work === undefined) rmSync(work, { recursive: true, force: true }); From e7a088f90420ca6de0d81675e6e95989a60eb215 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:58:11 -0500 Subject: [PATCH 02/49] Key the executable cache by the function layout's contents The early executable cache key covered the profile but not the function layout, so a build that added, changed or edited a layout could restore the executable built with the previous order. The key now includes the digest of the layout file's bytes. An unreadable layout fails the build with SC3002 before the cache lookup. --- .../src/executable/executable-cache.test.ts | 3 +- .../src/executable/executable-cache.ts | 4 ++ packages/compiler/src/index.ts | 20 ++++++++++ .../compiler/test/function-layout.test.ts | 38 ++++++++++++++++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index afae8b2c17..68aedbc33f 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -220,7 +220,7 @@ test("early executable cache misses on source and resolution changes", async () expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull(); }); -test("early executable keys isolate compile modes, paths, implementation, checker, and FFI manifests", async () => { +test("early executable keys isolate compile modes, paths, implementation, checker, function layouts, and FFI manifests", async () => { const f = await fixture(); const tracker = new FrontendInputTracker(); tracker.run(() => trackedReadFile(f.source)); @@ -241,6 +241,7 @@ test("early executable keys isolate compile modes, paths, implementation, checke { ...f.options, targetCpu: "x86-64-v4" }, { ...f.options, profile: "profile-generate:/tmp/%p.profraw" }, { ...f.options, profile: "profile-use:digest" }, + { ...f.options, functionLayout: "layout-digest" }, { ...f.options, checker: "tsc-ts-executable-identity" }, { ...f.options, npmStatic: "auto" }, { ...f.options, npmStatic: ["commander"] }, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index f26c4c89a0..ed351e809f 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -148,6 +148,8 @@ export interface EarlyExecutableCacheOptions { /** Omitted without --profile-generate/--profile-use; otherwise the * pattern, or the digest of the profile's bytes. */ profile?: string; + /** Omitted without a function layout; otherwise the digest of its bytes. */ + functionLayout?: string; /** Omitted for the default tsgo checker; with SCRIPTC_CHECKER=tsc-ts, the * tsc-ts executable's file identity. */ checker?: string; @@ -290,6 +292,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string { ...(options.detachedExit ? ["detached-exit"] : []), ...targetCpuKeyParts(options.targetCpu), ...(options.profile !== undefined ? [options.profile] : []), + ...(options.functionLayout !== undefined ? [`layout:${options.functionLayout}`] : []), ...(options.checker !== undefined ? [`checker:${options.checker}`] : []), ...(options.youngNursery ? ["young-nursery"] : []), options.npmStatic === null @@ -333,6 +336,7 @@ function routeKey(options: Omit { + if (opts.functionLayout === undefined) return undefined; + const path = resolve(opts.functionLayout); + let bytes: Buffer; + try { + bytes = await readFile(path); + } catch (err) { + throw new Error( + `cannot read function layout ${path}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + return createHash("sha256").update(bytes).digest("hex"); +} + /** The request's function layout. */ async function readFunctionLayout( opts: CompileRequestOptions, @@ -1546,9 +1563,11 @@ async function compileTracked( platform: buildPlatform, }); let profile: NativeProfileOptions | undefined; + let functionLayoutDigest: string | undefined; try { if (profileRefusal !== null) throw new Error(profileRefusal); profile = await readNativeProfile(opts); + functionLayoutDigest = await functionLayoutKey(opts); } catch (err) { return { ok: false, @@ -1583,6 +1602,7 @@ async function compileTracked( ...(opts.detachedExit === true ? { detachedExit: true as const } : {}), ...targetCpuField(opts.targetCpu), ...(profile === undefined ? {} : { profile: nativeProfileKey(profile) }), + ...(functionLayoutDigest === undefined ? {} : { functionLayout: functionLayoutDigest }), ...checkerField(), ...(youngNurseryRequested() ? { youngNursery: true as const } : {}), npmStatic: opts.npmStatic ?? null, diff --git a/packages/compiler/test/function-layout.test.ts b/packages/compiler/test/function-layout.test.ts index 8619c5b342..ecf4af7d9c 100644 --- a/packages/compiler/test/function-layout.test.ts +++ b/packages/compiler/test/function-layout.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { gzipSync } from "node:zlib"; -import { expect, test } from "vitest"; +import { expect, test, vi } from "vitest"; import { compile } from "../src/index.js"; import { functionLayoutKey, @@ -145,3 +145,39 @@ console.log(points.map(norm).join(","), points[0]!.x === 3 ? "three" : "other"); await rm(dir, { recursive: true, force: true }); } }); + +test("a changed function layout misses the executable cache", async () => { + vi.stubEnv("SCRIPTC_NO_CACHE", undefined); + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-cache-")); + try { + vi.stubEnv("SCRIPTC_CACHE_DIR", join(dir, "cache")); + const entry = join(dir, "main.ts"); + await writeFile(entry, source); + const exe = join(dir, "main"); + const llvm = join(dir, "main.ll"); + const build = async (functionLayout?: string) => { + const built = await compile(entry, { + outputKind: "exe", + outDir: dir, + outPath: exe, + ...(functionLayout === undefined ? {} : { functionLayout }), + }); + expect(built.ok, JSON.stringify(built.diagnostics)).toBe(true); + return definitions(await readFile(llvm, "utf8")); + }; + const plain = await build(); + const third = plain.find((d) => /^sc_b?f_third$/.test(d.name))!.name; + const first = plain.find((d) => /^sc_b?f_first$/.test(d.name))!.name; + const layout = join(dir, "layout"); + await writeFile(layout, `1 ${third}\n`); + expect((await build(layout)).find((d) => d.name === third)!.group).toBe("layout-1"); + // The same path with other contents is another layout. + await writeFile(layout, `1 ${first}\n`); + const edited = await build(layout); + expect(edited.find((d) => d.name === first)!.group).toBe("layout-1"); + expect(edited.find((d) => d.name === third)!.group).not.toMatch(/^layout-/); + } finally { + vi.unstubAllEnvs(); + await rm(dir, { recursive: true, force: true }); + } +}); From 20a4a92aa604ee1a6159cdd78d8bd38c1c9d3146 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:05:55 -0700 Subject: [PATCH 03/49] Reserve the rc word's high half for layout ids The single object model keeps an object's layout id in the high half of its rc word, beside the program-lifetime and frozen bits. Class ids already live there; this names the encoding and moves the runtime's tests that may see an object with an id onto the count: - scr_rc_layout_id reads the 30-bit layout id; scr_rc_class_id is now that id. - scr_rc_immortal tests the count, and scr_rc_frozen and Object.freeze of a record use it instead of comparing the whole word with SIZE_MAX. - Publication writes only the count of any object with a layout id, so an immortal object keeps its id, as a class instance keeps its class id. - classIdRcWord refuses a class id outside the layout id range. No object other than a class instance carries an id yet, so behavior and emitted code are unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/backend/llvm/classes.ts | 13 +++- packages/runtime/src/scr_json.c | 2 +- packages/runtime/src/scr_publish.c | 6 +- packages/runtime/src/scr_rc.h | 68 ++++++++++++++----- packages/runtime/src/scr_rc.test.c | 47 +++++++++++++ packages/runtime/src/scr_rc.test.ts | 36 ++++++++++ 6 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 packages/runtime/src/scr_rc.test.c create mode 100644 packages/runtime/src/scr_rc.test.ts diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index 699755157e..f71f96a3d5 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -573,13 +573,20 @@ export function classIdLoadLines( * 64-bit word. The count is the i32 at offset 0. */ export const CLASS_ID_OFFSET = 4; +/** The bound on layout ids (scr_rc.h SCR_RC_LAYOUT_ID_LIMIT): the high + * half of a 64-bit rc word keeps the layout id in its low 30 bits, beside + * the program-lifetime and frozen bits. A class id is the layout id of its + * class's canonical layout. */ +export const LAYOUT_ID_LIMIT = 0x3fffffff; + /** The rc word an LlClassMeta.idInRc instance is created with: the class * id, its preorder number plus 1, in the high 32 bits, and in the low 32 bits * a count of 1, or for a program-lifetime instance the midpoint of the 32-bit - * count (as programLifetimeRc is of the 32-bit word). The id is never 0, - * 0x80000000 or 0xffffffff, the high halves of other objects' rc words, so - * the runtime can tell an object with a class id (scr_rc.h scr_rc_class_id). */ + * count (as programLifetimeRc is of the 32-bit word). The id is a layout id + * (LAYOUT_ID_LIMIT), so it never sets the program-lifetime or frozen bit + * and the runtime reads it back (scr_rc.h scr_rc_layout_id). */ export function classIdRcWord(meta: LlClassMeta, lifetime: boolean): string { + if (meta.pre + 1 >= LAYOUT_ID_LIMIT) throw new Error(`class id ${meta.pre + 1} is not a layout id`); return (BigInt(meta.pre + 1) * 2n ** 32n + (lifetime ? 2n ** 31n : 1n)).toString(); } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index e19ea65cc9..c4c9b0ba9c 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2564,7 +2564,7 @@ static void scr_dyn_record_view_freeze(ScrDyn *view) { void scr_record_freeze(void *record) { size_t *rc = record; - if (*rc != SIZE_MAX) *rc |= SCR_RC_FROZEN; + if (!scr_rc_immortal(record)) *rc |= SCR_RC_FROZEN; } void scr_record_throw_frozen(ScrStr *key, ScrStr *owner, double operation) { diff --git a/packages/runtime/src/scr_publish.c b/packages/runtime/src/scr_publish.c index afbe4d6af5..8d11d3806e 100644 --- a/packages/runtime/src/scr_publish.c +++ b/packages/runtime/src/scr_publish.c @@ -128,8 +128,10 @@ bool scr_pub_mark(ScrPub *ctx, void *obj, bool cyc, int kind) { size_t *rc = obj; if (SCR_RC_PUBLISHED(obj) || ctx->refusal) return false; bool compact = (kind & SCR_PUB_COMPACT) != 0; - /* Both log the low 32 bits: a count, or a compact header word. */ - bool class_id = compact || (kind & SCR_PUB_CLASS_ID) != 0; + /* Both log the low 32 bits: a count, or a compact header word. An + * object with a layout id in its rc word (scr_rc_layout_id) keeps it, as + * a class instance keeps its class id. */ + bool class_id = compact || (kind & SCR_PUB_CLASS_ID) != 0 || scr_rc_layout_id(obj) != 0; kind &= ~(SCR_PUB_CLASS_ID | SCR_PUB_COMPACT); if (ctx->logged == ctx->log_cap) ctx->log = scr_pub_grow(ctx->log, &ctx->log_cap, sizeof *ctx->log, ctx->log_inline); diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index 171e420c20..34c9fdc1db 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -8,13 +8,27 @@ /* ── reference counts ───────────────────────────────────────────────── * Every heap object's first member is its `size_t rc` word. On 64-bit - * targets the reference count is the word's low 32 bits: an emitted - * instance of a class in a program-rooted hierarchy keeps its class id (its - * class's preorder number + 1) in the high 32 bits (llvm/classes.ts - * classIdRcWord), and no other object's count reaches 2^32. A count of SCR_RC_IMMORTAL marks - * an immortal object; for every object but those class instances the whole - * word is then SIZE_MAX. Code that reads or writes the count of an object of - * any kind goes through SCR_RC_COUNT. */ + * targets the word is + * + * bits 0-31 the reference count (SCR_RC_COUNT) + * bit 62 F: a frozen record (SCR_RC_FROZEN) + * bit 63 P: a program-lifetime record (llvm/classes.ts + * programLifetimeRc) + * bits 32-61 the layout id (scr_rc_layout_id) + * + * and no object's count reaches 2^32. The layout id names the object's + * layout in the single object model (scr_layout.h); 0 means the object + * never needed one. An emitted instance of a class in a program-rooted + * hierarchy keeps its class id (its class's preorder number + 1) there + * (llvm/classes.ts classIdRcWord): a class id is the layout id of its + * class's canonical layout. A count of SCR_RC_IMMORTAL marks an immortal + * object. Publication and the runtime's own immortal objects write the + * whole word as SIZE_MAX when the object has no layout id, and write only + * the count when it has one, so an immortal object keeps its layout id + * (scr_rc_immortal tests the count; a high half of all ones reads as no + * layout id). Code that reads or writes the count of an object of any + * kind goes through SCR_RC_COUNT; code that may see an object with a layout + * id tests immortality with scr_rc_immortal, never against SIZE_MAX. */ #if SIZE_MAX > UINT32_MAX typedef uint32_t ScrRcCount; #ifdef __BYTE_ORDER__ @@ -58,25 +72,47 @@ typedef size_t ScrRcCount; #define SCR_RC_FROZEN ((size_t)1 << 30) #define SCR_RC_REFS(obj) (SCR_RC_COUNT(obj) == SCR_RC_IMMORTAL ? SCR_RC_IMMORTAL : SCR_RC_COUNT(obj) & ~SCR_RC_FROZEN) #endif -static inline bool scr_rc_frozen(const void *obj) { - size_t word = *(const size_t *)obj; - return word != SIZE_MAX && (word & SCR_RC_FROZEN) != 0; +/* Whether `obj`, an object whose rc word is a count (not a compact class + * instance), is immortal: its count is SCR_RC_IMMORTAL, whatever its high + * half holds. */ +static inline bool scr_rc_immortal(const void *obj) { + return SCR_RC_COUNT(obj) == SCR_RC_IMMORTAL; } -/* The class id in the high half of `obj`'s rc word, or 0 for an object - * without one: the high half of any other object's word is 0, 0xffffffff - * (immortal), 0x80000000 (program-lifetime) or 0x40000000 (a frozen record), - * and a class id is never one of these. */ -static inline uint32_t scr_rc_class_id(const void *obj) { +static inline bool scr_rc_frozen(const void *obj) { + return !scr_rc_immortal(obj) && (*(const size_t *)obj & SCR_RC_FROZEN) != 0; +} + +/* The high half of the rc word on 64-bit targets: the program-lifetime + * bit, the frozen bit and the layout id (see the layout above). 32-bit + * targets have no high half; their objects keep a layout id elsewhere when + * they need one (scr_layout.h). */ +#define SCR_RC_HIGH_LIFETIME UINT32_C(0x80000000) +#define SCR_RC_HIGH_FROZEN UINT32_C(0x40000000) +#define SCR_RC_LAYOUT_ID_MASK UINT32_C(0x3fffffff) +/* Layout ids are below this bound: 2^30 - 1 is the id bits of an all-ones + * high half, which reads as no id. */ +#define SCR_RC_LAYOUT_ID_LIMIT SCR_RC_LAYOUT_ID_MASK + +/* The layout id in the high half of `obj`'s rc word, or 0 for an object + * without one. The whole-word immortal encoding (SIZE_MAX) has no id. */ +static inline uint32_t scr_rc_layout_id(const void *obj) { #if SIZE_MAX > UINT32_MAX uint32_t high = (uint32_t)(*(const size_t *)obj >> 32); - return high == UINT32_MAX || high == UINT32_C(0x80000000) || high == UINT32_C(0x40000000) ? 0 : high; + return high == UINT32_MAX ? 0 : high & SCR_RC_LAYOUT_ID_MASK; #else (void)obj; return 0; #endif } +/* The class id in the high half of `obj`'s rc word, or 0 for an object + * without one. A class id is the layout id of the class's canonical + * layout, and no object of any other kind has a layout id yet. */ +static inline uint32_t scr_rc_class_id(const void *obj) { + return scr_rc_layout_id(obj); +} + /* ── cycle collection (scr_cycle.c) ─────────────────────────────────── * Reference counting alone cannot free cycles, so every object that can * participate in one — capture boxes, heap closures, unions, promises, and diff --git a/packages/runtime/src/scr_rc.test.c b/packages/runtime/src/scr_rc.test.c new file mode 100644 index 0000000000..159235ce95 --- /dev/null +++ b/packages/runtime/src/scr_rc.test.c @@ -0,0 +1,47 @@ +#undef NDEBUG +#include "scr_rc.h" +#include +#include + +/* The rc word's encodings (scr_rc.h): the count in the low half, and the + * program-lifetime bit, the frozen bit and the layout id in the high half. + * Immortality is a count of all ones; a layout id survives it. */ +static size_t word(uint32_t high, uint32_t count) { + return ((size_t)high << 32) | count; +} + +int main(void) { +#if SIZE_MAX > UINT32_MAX + /* An ordinary object: no layout id, not frozen, not immortal. */ + size_t plain = word(0, 3); + assert(scr_rc_layout_id(&plain) == 0 && !scr_rc_frozen(&plain) && !scr_rc_immortal(&plain)); + /* The whole-word immortal encoding has no layout id and is not frozen. */ + size_t immortal = SIZE_MAX; + assert(scr_rc_layout_id(&immortal) == 0 && scr_rc_immortal(&immortal) && !scr_rc_frozen(&immortal)); + /* A program-lifetime record (llvm/classes.ts programLifetimeRc) and a + * frozen record have no layout id. */ + size_t lifetime = word(SCR_RC_HIGH_LIFETIME, 0); + assert(scr_rc_layout_id(&lifetime) == 0 && !scr_rc_immortal(&lifetime)); + size_t frozen = word(SCR_RC_HIGH_FROZEN, 2); + assert(scr_rc_layout_id(&frozen) == 0 && scr_rc_frozen(&frozen)); + /* A class instance keeps its class id, which is its layout id, beside + * its count, also once immortal, frozen or program-lifetime. */ + size_t instance = word(7, 1); + assert(scr_rc_layout_id(&instance) == 7 && scr_rc_class_id(&instance) == 7); + SCR_RC_COUNT(&instance) = SCR_RC_IMMORTAL; + assert(scr_rc_immortal(&instance) && scr_rc_layout_id(&instance) == 7 && !scr_rc_frozen(&instance)); + size_t frozen_id = word(SCR_RC_HIGH_FROZEN | 9, 1); + assert(scr_rc_layout_id(&frozen_id) == 9 && scr_rc_frozen(&frozen_id)); + size_t lifetime_id = word(SCR_RC_HIGH_LIFETIME | 11, UINT32_C(0x80000000)); + assert(scr_rc_layout_id(&lifetime_id) == 11 && !scr_rc_immortal(&lifetime_id)); + /* The largest layout id. */ + size_t top = word(SCR_RC_LAYOUT_ID_LIMIT - 1, 1); + assert(scr_rc_layout_id(&top) == SCR_RC_LAYOUT_ID_LIMIT - 1); +#else + size_t plain = 3, immortal = SIZE_MAX; + assert(scr_rc_layout_id(&plain) == 0 && !scr_rc_immortal(&plain)); + assert(scr_rc_immortal(&immortal) && !scr_rc_frozen(&immortal)); +#endif + puts("rc word encodings"); + return 0; +} diff --git a/packages/runtime/src/scr_rc.test.ts b/packages/runtime/src/scr_rc.test.ts new file mode 100644 index 0000000000..181d0bebac --- /dev/null +++ b/packages/runtime/src/scr_rc.test.ts @@ -0,0 +1,36 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const exec = promisify(execFile); + +/* The rc word helpers are inline in scr_rc.h, so the test links alone. */ +test.skipIf(process.platform === "win32")( + "the rc word keeps the count, the lifetime and frozen bits and the layout id apart", + async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-rc-")); + try { + const bin = join(dir, "rc"); + await exec("clang", [ + "-std=c11", + "-O1", + "-g", + "-fsanitize=address,undefined", + join(import.meta.dirname, "scr_rc.test.c"), + "-o", + bin, + ]); + const result = await exec(bin, [], { + timeout: 60000, + env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, + }); + expect(result.stdout).toBe("rc word encodings\n"); + expect(result.stderr).toBe(""); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }, +); From d74cc3b8bdd7e140bc1c81d3341da680d7bb540e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:16:41 -0700 Subject: [PATCH 04/49] Report which type failures localized failure would turn into dynamic slots Stage L0 of the localized-failure design, report-only: no lowering changes. A type with no static lowering poisons the slot that holds it, and the poison rejects the declaration around it (a class, a signature, a declaration statement); each later use then fails on its own line. This records, on the committed diagnostics, where each type failure sits, whether a checked dynamic slot would keep the program's meaning, and which rejected declarations each failing statement used. coverage --explain-dynamic turns that into the would-be dynamic slot list with the cascade each slot clears, and the counts of errors that would disappear through stages L2 and L4. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/coverage/report.ts | 60 +++++- .../compiler/src/diagnostics/diagnostic.ts | 58 ++++++ packages/compiler/src/diagnostics/envelope.ts | 21 ++ .../src/frontend/lowering/lower-classes.ts | 9 +- .../src/frontend/lowering/lower-stmts.ts | 41 +++- .../compiler/src/frontend/lowering/lowerer.ts | 187 ++++++++++++++--- .../src/frontend/lowering/type-failure.ts | 195 ++++++++++++++++++ .../compiler/src/frontend/tier-analysis.ts | 183 +++++++++++++++- tests/coverage-fixtures/localized-failure.ts | 53 +++++ .../coverage-explain-localized-failure.txt | 49 +++++ tests/harness/coverage.test.ts | 32 +++ 11 files changed, 842 insertions(+), 46 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/type-failure.ts create mode 100644 tests/coverage-fixtures/localized-failure.ts create mode 100644 tests/harness/__snapshots__/coverage-explain-localized-failure.txt diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 58a2cafa64..dabbc96176 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -24,7 +24,11 @@ import { import { renderDiagnostics } from "../diagnostics/render.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { ProvenanceSources } from "../frontend/provenance-registry.js"; -import type { DemotionUnit, TierReport } from "../frontend/tier-analysis.js"; +import type { + DemotionUnit, + LocalizedFailureReport, + TierReport, +} from "../frontend/tier-analysis.js"; import type { WasmModuleReport } from "../wasm/discover.js"; import { describe as describeReason, @@ -668,6 +672,26 @@ function tiersEnvelope(tiers: TierReport, locator: SourceLocator): TiersEnvelope ...at(r.loc), })), units: tiers.units.map((u) => ({ ...unit(u), requests: u.requests, sites: u.sites })), + localizedFailure: { + errors: tiers.localizedFailure.errors, + typeFailures: tiers.localizedFailure.typeFailures, + cascades: tiers.localizedFailure.cascades, + clearedThroughL2: tiers.localizedFailure.clearedThroughL2, + clearedThroughL4: tiers.localizedFailure.clearedThroughL4, + remainingByCode: tiers.localizedFailure.remainingByCode, + slots: tiers.localizedFailure.slots.map((slot) => ({ + ...at(slot.loc), + slot: slot.slot, + name: slot.name, + declaredType: slot.declaredType, + family: slot.family, + code: slot.code, + message: slot.message, + stage: slot.stage, + ...(slot.rejects !== null ? { rejects: slot.rejects } : {}), + cascade: slot.cascade, + })), + }, }; } @@ -695,6 +719,39 @@ function unitMatches(u: DemotionUnit, query: string): boolean { ); } +const SLOTS_SHOWN = 40; + +/** The localized-failure section of --explain-dynamic (report-only): the + * committed type failures that would become checked dynamic slots, and + * the cascade each one causes today. */ +function renderLocalizedFailure( + report: LocalizedFailureReport, + out: string[], + c: (code: string, s: string) => string, + where: (loc: { file: string; start: number }) => string, +): void { + if (report.typeFailures.degradable === 0 && report.typeFailures.refused === 0) return; + out.push(""); + out.push( + ` ${report.slots.length} would-be dynamic slot${report.slots.length === 1 ? "" : "s"} ${c(DIM, "(localized failure, report-only: type failures a checked dynamic slot would hold)")}`, + ); + out.push( + ` ${c(DIM, `${report.errors} errors: ${report.clearedThroughL2} clear with declaration slots (L1-L2), ${report.clearedThroughL4} with member and expression slots too (L3-L4); ${report.cascades} cascade${report.cascades === 1 ? "" : "s"} attributed; ${report.typeFailures.refused} type failure${report.typeFailures.refused === 1 ? " stays" : "s stay"} refused`)}`, + ); + for (const slot of report.slots.slice(0, SLOTS_SHOWN)) { + const cascade = slot.cascade > 0 ? `, clears ${slot.cascade}` : ""; + out.push( + ` ${slot.stage} ${slot.slot} ${slot.name}: ${capMessage(slot.declaredType)} ${c(DIM, `${where(slot.loc)} (${slot.code} ${slot.family}${cascade})`)}`, + ); + if (slot.rejects !== null) + out.push(` ${c(DIM, `rejects ${slot.rejects.split(" @")[0]}`)}`); + } + if (report.slots.length > SLOTS_SHOWN) + out.push( + ` ${c(DIM, `${report.slots.length - SLOTS_SHOWN} more (--print=diagnostics lists all)`)}`, + ); +} + /** `scriptc coverage --explain-dynamic[=]`: the dynamic tier today * and the demotion chains the report-only analysis computed. With a query, * only units whose name matches it (`x`, `Point.x`, `parse:x`). */ @@ -735,6 +792,7 @@ export function renderExplainDynamic( (excluded > 0 ? c(DIM, `, ${excluded} excluded by policy`) : ""), ); } + if (query === undefined) renderLocalizedFailure(tiers.localizedFailure, out, c, where); const units = tiers.units.filter((u) => query === undefined || unitMatches(u, query)); if (query !== undefined && units.length === 0) { out.push(` no demoted unit matches '${query}'`); diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 8010cbb175..534e03c076 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -110,6 +110,64 @@ export interface ScrDiagnostic { * and class member body: it throws when the module initializes, which is * program startup for a module the entry imports. */ startup?: true; + /** Localized failure, report-only (stage L0): set where a type with no + * static lowering poisoned a slot (Lowerer.badType and the dyn class + * field policy). Never part of the rendered message. */ + typeFailure?: TypeFailure; + /** Localized failure, report-only: the declaration this diagnostic + * rejected, as a failure-group key. Set when a reference flushes a + * deferred declaration's diagnostics, and on the diagnostics of a + * declaration statement whose bindings were blocked. */ + rejects?: string; + /** Localized failure, report-only: the failure groups (rejected + * declarations, blocked statements) that the statement committing this + * diagnostic used. A cascade clears when every one of them would + * degrade instead of failing. */ + cause?: readonly string[]; +} + +/** Where a type failure sits (localized-failure design §3.6): a + * declaration slot, a member of a declared type, or an expression's own + * type. */ +export type TypeFailureSlot = + | "field" + | "param" + | "return" + | "local" + | "global" + | "loop" + | "member" + | "expression"; + +/** Which badType branch (or policy fence) refused the type. */ +export type TypeFailureFamily = + | "node-types" + | "fetch-ambient" + | "enum-object" + | "stdlib-index" + | "index-signature" + | "package" + | "generic-signature" + | "overloads" + | "intersection" + | "component" + | "stdlib-nominal" + | "record-member" + | "unsupported" + | "dyn-field-policy"; + +/** A type failure's structured payload: what the commit points of the + * localized-failure design inspect (PoisonError.failure carries the same + * object). `degradable` is the §3.7 classification: a checked-dynamic slot + * would keep the program's meaning. */ +export interface TypeFailure { + slot: TypeFailureSlot; + /** The slot's name (`_customTools`, `listAll`, `x`), or the expression + * text for expression slots. */ + name: string; + declaredType: string; + family: TypeFailureFamily; + degradable: boolean; } /** SC3002–SC3004 — native-helper target, installation, and execution diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index bdb545ff3f..e64f35824d 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -114,6 +114,27 @@ export interface TiersEnvelope { message: string; })[]; units: (TierUnitPosition & { requests: number; sites: number })[]; + /** Localized failure (report-only): committed type failures that would + * become checked dynamic slots, and what they would clear. */ + localizedFailure: { + errors: number; + typeFailures: { degradable: number; refused: number }; + cascades: number; + clearedThroughL2: number; + clearedThroughL4: number; + remainingByCode: Record; + slots: (SourcePosition & { + slot: string; + name: string; + declaredType: string; + family: string; + code: string; + message: string; + stage: string; + rejects?: string; + cascade: number; + })[]; + }; } export interface DiagnosticsEnvelope { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index d941d7b60e..2c0145c340 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -2542,8 +2542,7 @@ export function collectClassShapeInner( (member.type !== undefined || (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) ) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( member.name, `${dynFieldTypeSpelling(lowerer, member.name)}-typed static fields`, ); @@ -3059,8 +3058,7 @@ export function collectClassShapeInner( !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) ) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( member.name, `${dynFieldTypeSpelling(lowerer, member.name)}-typed class fields`, ); @@ -3197,8 +3195,7 @@ export function collectClassShapeInner( if (type.kind === "void") lowerer.badType(p.name, lowerer.typeOf(p.name)); // The class-field dyn rule verbatim (KEEP NARROW). if (type.kind === "dyn" && !lowerer.classFieldStoresDyn(lowerer.typeOf(p))) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( p.name, `${dynFieldTypeSpelling(lowerer, p)}-typed class fields`, ); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 84196476a6..4cf3a342cb 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -41,6 +41,8 @@ import { isTimeValueDateDeclaration } from "./date-values.js"; import { bindingSource } from "../binding-source.js"; import { lowerConstructorReturn } from "./constructor-returns.js"; import type { Lowerer } from "./lowerer.js"; +import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import { statementGroupKey } from "./type-failure.js"; import { cjsExportValueByName, cjsNamespaceDestructureOf, @@ -750,6 +752,8 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir if (!lowerer.suppressStats) lowerer.countStatement(stmt, sf?.fileName); const diagsBefore = lowerer.diags.length; const sinkMark = lowerer.diagSinkMark(); + const touched = new Set(); + lowerer.failureTouches.push(touched); try { let lowered: IrStmt | IrStmt[] | null; let appended = false; @@ -813,7 +817,17 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir throw e; } if (!lowerer.suppressStats) lowerer.countStatementFailed(stmt, sf?.fileName); - lowerer.noteBlockedBindings(stmt); + // Localized failure (report-only): the statement's committed + // diagnostics that are not themselves type failures or rejected + // declarations are cascades of the rejected declarations it used. + const committed = + lowerer.diagSink === null && !deferFences ? lowerer.diags.slice(diagsBefore) : []; + if (touched.size > 0) { + const cause = [...touched]; + for (const d of committed) + if (d.typeFailure === undefined && d.rejects === undefined) d.cause ??= cause; + } + lowerer.noteBlockedBindings(stmt, committed); // The runtime-fence conversion (JS sources, direct diagnostics // only — a capture wrapper in force means collection owns the // report). The statement's diagnostics move OFF the build and @@ -837,6 +851,8 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir const fence = lowerer.capturedRuntimeFence(sinkMark, stmt); if (fence) out.push(fence); } + } finally { + lowerer.failureTouches.pop(); } } // Each acquisition protects the remainder of its lexical statement @@ -1522,7 +1538,11 @@ function noteEarlyReadVar(lowerer: Lowerer, symbol: ts.Symbol, local: IrLocal): * lowerVarDecl's salvage path declares the local when it can, and * resolution checks locals/globals before the fallthroughs consult * blockedBindings. */ -export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void { +export function noteBlockedBindings( + lowerer: Lowerer, + stmt: ts.Statement, + diags: readonly ScrDiagnostic[] = [], +): void { let list: ts.VariableDeclarationList | null = null; if (ts.isVariableStatement(stmt)) list = stmt.declarationList; else if ( @@ -1533,12 +1553,20 @@ export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void list = stmt.initializer; } if (!list) return; + // Localized failure (report-only): the bindings form one failure group, + // the statement, whose diagnostics are the group's. + const group = statementGroupKey(stmt); + let bound = false; for (const decl of list.declarations.nodes) { for (const nameNode of boundIdentifiersOf(decl.name)) { const symbol = lowerer.checker.getSymbolAtLocation(nameNode); - if (symbol) lowerer.blockedBindings.add(symbol); + if (!symbol) continue; + lowerer.blockedBindings.add(symbol); + if (!lowerer.rejectedGroups.has(symbol)) lowerer.rejectedGroups.set(symbol, group); + bound = true; } } + if (bound) for (const d of diags) d.rejects ??= group; } /** True when a reference/assignment fell through resolution because the @@ -1548,12 +1576,13 @@ export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void * flushed by the emit pass). */ export function isBlockedBinding(lowerer: Lowerer, symbol: ts.Symbol | null): boolean { if (!symbol) return false; - return ( + const blocked = lowerer.blockedBindings.has(symbol) || lowerer.deferredDiags.has(symbol) || lowerer.flushedSymbols.has(symbol) || - lowerer.alreadyFlushed.has(symbol) - ); + lowerer.alreadyFlushed.has(symbol); + if (blocked) lowerer.touchRejected(symbol); + return blocked; } /** Lowers a statement in a fresh lexical scope (if/while/for bodies). */ diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 634c361c28..65b66c298a 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -204,7 +204,12 @@ import { isBundledShimForwarder } from "../package-shims.js"; import { erasedTypeOnlyImport } from "../type-only-imports.js"; import { literalValues } from "../literal-values.js"; import { discriminantOwners, literalUnionArm, type UnionLiteral } from "../union-discriminants.js"; -import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import type { + ScrDiagnostic, + TypeFailure, + TypeFailureFamily, +} from "../../diagnostics/diagnostic.js"; +import { declarationGroupKey, typeFailureOf } from "./type-failure.js"; import { unsupportedAnyOperationDiag, blockedBindingUseDiag, @@ -713,7 +718,16 @@ interface GenericDemandOwner { classDemands: ClassInfo[]; } -export class PoisonError extends Error {} +export class PoisonError extends Error { + /** Set when a type with no static lowering poisoned a slot (badType and + * the dyn class field policy): what failed, for the localized-failure + * commit points. Report-only in stage L0. */ + readonly failure: TypeFailure | undefined; + constructor(failure?: TypeFailure) { + super(); + this.failure = failure; + } +} /** A function-shaped value that exists only in the compiler's static * projection graph. Declared-function aliases point at the original @@ -3697,6 +3711,15 @@ export class Lowerer { * the fallthroughs report the inherited-blocker cascade (SC2004) * instead of misattributing the reference. */ readonly blockedBindings = new Set(); + /** Localized failure, report-only: the failure group (type-failure.ts) + * of each declaration whose rejection reached the build — a flushed + * deferred declaration, or a binding of a poisoned declaration + * statement. */ + readonly rejectedGroups = new Map(); + /** One frame per statement lowerStmts is lowering: the failure groups + * its lowering touched. A statement that then poisons attributes its + * diagnostics to them (ScrDiagnostic.cause). */ + readonly failureTouches: Set[] = []; /** True while collectProgram runs: resolution helpers must not flush * deferred diagnostics (collection itself resolves symbols — extends * clauses — and collection order must not decide what reports). */ @@ -9177,7 +9200,11 @@ export class Lowerer { this.diagSink.push(d); return; } - if (pending) this.commitFlush(pending.symbol, pending.source); + if (pending) { + this.commitFlush(pending.symbol, pending.source); + const group = this.rejectedGroups.get(pending.symbol); + if (group !== undefined) d.rejects ??= group; + } // One site, one report: some declarations map a type twice (module // globals pre-register before their initializers lower) — an exact // duplicate (code + span + message) adds noise, not information. @@ -9232,6 +9259,35 @@ export class Lowerer { return (this.typeOf(node).flags & ts.TypeFlags.Any) !== 0; } + /** badType's commit: the diagnostic carries the structured type + * failure (report-only in stage L0), and so does the poison. */ + private typeFail( + node: ts.Node, + type: ts.Type, + family: TypeFailureFamily, + diag: ScrDiagnostic, + ): never { + const failure = typeFailureOf(node, this.checker.typeToString(type), family); + diag.typeFailure = failure; + this.pushDiag(diag); + throw new PoisonError(failure); + } + + /** The SC1090 refusal of checked-dynamic class fields in TypeScript + * (classFieldStoresDyn): a policy fence, not a missing representation, + * so its failure is recorded as degradable. */ + dynFieldPolicyFence(node: ts.Node, feature: string): never { + const diag = unsupportedDiag("SC1090", locOf(node), feature); + const failure = typeFailureOf( + node, + this.checker.typeToString(this.typeOf(node)), + "dyn-field-policy", + ); + diag.typeFailure = failure; + this.pushDiag(diag); + throw new PoisonError(failure); + } + badType(node: ts.Node, type: ts.Type): never { const widened = this.checker.getBaseTypeOfLiteralType(type); // Types declared by the ADOPTED @types/node (Buffer, NodeJS.Timeout, @@ -9243,8 +9299,12 @@ export class Lowerer { // applies to node-typed values. const typeSym = widened.getAliasSymbol() ?? widened.getSymbol(); if (this.nodeTypesOnlySymbol(typeSym)) { - this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node), undefined, true)); - throw new PoisonError(); + this.typeFail( + node, + type, + "node-types", + noLoweringDiag(this.checker.typeToString(type), locOf(node), undefined, true), + ); } // Engine-backed ambient TYPES in a STATIC build report the per-site // SC2012 rather than the generic supported-types recitation. Native @@ -9255,10 +9315,12 @@ export class Lowerer { (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(typeSym)) && this.isStdlibSymbol(typeSym) ) { - this.pushDiag( + this.typeFail( + node, + type, + "fetch-ambient", unsupportedNativeApiDiag(`a value of type '${ts.symbolName(typeSym)}'`, locOf(node)), ); - throw new PoisonError(); } // ENUM OBJECTS (`typeof e` — a numeric enum's type carries the // reverse-map index signature, a string enum's just its members): @@ -9267,14 +9329,16 @@ export class Lowerer { // as the per-site identifier fence — never the index-signature // recitation. if (typeSym && (typeSym.flags & ts.SymbolFlags.Enum) !== 0) { - this.pushDiag( + this.typeFail( + node, + type, + "enum-object", unsupportedDiag( "SC1090", locOf(node), `enum objects as values ('${ts.symbolName(typeSym)}' — member reads like '${ts.symbolName(typeSym)}.X' compile to constants; the object itself has no runtime representation)`, ), ); - throw new PoisonError(); } // Would-be records with INDEX SIGNATURES (`Record`, // `{ [k: string]: T }`) get the index-signature fence (SC2006) naming @@ -9304,19 +9368,26 @@ export class Lowerer { this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { - this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "stdlib-index", + noLoweringDiag(this.checker.typeToString(type), locOf(node)), + ); } - this.pushDiag(indexSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "index-signature", + indexSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // Attribute an unsupported package type to its package. { const pkg = this.npmPackageOf(widened); if (pkg) { - this.pushDiag(unsupportedPackageValueDiag(pkg, locOf(node))); - throw new PoisonError(); + this.typeFail(node, type, "package", unsupportedPackageValueDiag(pkg, locOf(node))); } } // A type that keeps a GENERIC call signature (`(x: T) => T` slots, @@ -9332,8 +9403,12 @@ export class Lowerer { this.checker.getCallSignatures(p).some((s) => s.getTypeParameters().length > 0), ) ) { - this.pushDiag(genericSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "generic-signature", + genericSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } } // STANDARD-LIBRARY nominal provenance, decided once: interface/class @@ -9353,16 +9428,24 @@ export class Lowerer { // generic overload set keeps the monomorphization story: a value of a // multi-signature type has no single compiled signature to hold. if (!stdlibNominal && this.checker.getCallSignatures(widened).length > 1) { - this.pushDiag(overloadedSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "overloads", + overloadedSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // INTERSECTIONS that resolved to no lowering (SC2008). The ones that // compile never get here: member intersections intern through the // record path, callable hybrids map to '%call' records, and pinned // mixin instantiations resolve by chain structure. if (widened.isIntersectionType()) { - this.pushDiag(intersectionTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "intersection", + intersectionTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // SUPPORTED shapes over a component outside its slot (SC2009): the // Map/Set domains, array/tuple elements, union arms, function @@ -9374,8 +9457,12 @@ export class Lowerer { { const detail = describeComponentBlocker(widened, this.typeCtx); if (detail !== null) { - this.pushDiag(componentTypeDiag(this.checker.typeToString(type), detail, locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "component", + componentTypeDiag(this.checker.typeToString(type), detail, locOf(node)), + ); } } // STANDARD-LIBRARY nominal types with no lowering at all: the SC2020 @@ -9405,26 +9492,36 @@ export class Lowerer { TextDecoder: "TextDecoder instances support recognized labels, fatal/ignoreBOM option literals, and streaming UTF-8/UTF-16/single-byte decode; runtime option objects remain unsupported", }; - this.pushDiag( + this.typeFail( + node, + type, + "stdlib-nominal", noLoweringDiag( this.checker.typeToString(type), locOf(node), typeHints[ts.symbolNameOf(stdlibOwnSym) ?? ""], ), ); - throw new PoisonError(); } // USER record shapes blocked by ONE member (SC2009's record arm): // name the member and its type. { const detail = describeRecordMemberBlocker(widened, this.typeCtx); if (detail !== null) { - this.pushDiag(componentTypeDiag(this.checker.typeToString(type), detail, locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "record-member", + componentTypeDiag(this.checker.typeToString(type), detail, locOf(node)), + ); } } - this.pushDiag(unsupportedTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "unsupported", + unsupportedTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } /** The lib fence (SC2020): a reached use of standard-library surface @@ -12067,12 +12164,35 @@ export class Lowerer { this.reportDeferred(symbol); } + /** A statement's lowering used a declaration: if that declaration was + * rejected, the statement's own failure (if any) is its cascade. */ + touchRejected(symbol: ts.Symbol): void { + const group = this.rejectedGroups.get(symbol); + if (group === undefined) return; + const frame = this.failureTouches[this.failureTouches.length - 1]; + if (frame !== undefined) frame.add(group); + } + + /** Records a declaration whose deferred diagnostics reached the build: + * its failure group, on the group's diagnostics too. */ + private noteRejected(symbol: ts.Symbol, diags: readonly ScrDiagnostic[]): void { + let group = this.rejectedGroups.get(symbol); + if (group === undefined) { + group = declarationGroupKey(symbol); + this.rejectedGroups.set(symbol, group); + } + for (const d of diags) d.rejects ??= group; + } + /** flushDeferred without the collection-pass guard: for declarations * whose diagnostics must report eagerly (class statements with * declaration-time code, extended bases — collectClassShapeDeferring). */ reportDeferred(symbol: ts.Symbol): void { const diags = this.deferredDiags.get(symbol); - if (!diags) return; + if (!diags) { + this.touchRejected(symbol); + return; + } if (this.alreadyFlushed.has(symbol)) { this.deferredDiags.delete(symbol); // the emit pass reported these return; @@ -12080,7 +12200,9 @@ export class Lowerer { if (this.diagSink === null) { this.deferredDiags.delete(symbol); this.flushedSymbols.add(symbol); + this.noteRejected(symbol, diags); for (const d of diags) this.pushDiag(d); + this.touchRejected(symbol); return; } // A capture buffer is active (a probe, or another declaration's @@ -12097,6 +12219,7 @@ export class Lowerer { * (the declaration counts as flushed for the coverage remainder). */ private commitFlush(symbol: ts.Symbol, source: ScrDiagnostic): void { this.flushedSymbols.add(symbol); + this.noteRejected(symbol, [source]); const list = this.deferredDiags.get(symbol); if (!list) return; const index = list.indexOf(source); @@ -13222,8 +13345,8 @@ export class Lowerer { return lowerStmts(this, stmts); } - noteBlockedBindings(stmt: ts.Statement): void { - return noteBlockedBindings(this, stmt); + noteBlockedBindings(stmt: ts.Statement, diags?: readonly ScrDiagnostic[]): void { + return noteBlockedBindings(this, stmt, diags); } isBlockedBinding(symbol: ts.Symbol | null): boolean { diff --git a/packages/compiler/src/frontend/lowering/type-failure.ts b/packages/compiler/src/frontend/lowering/type-failure.ts new file mode 100644 index 0000000000..3502a9bbf7 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/type-failure.ts @@ -0,0 +1,195 @@ +/* Localized failure, stage L0 (report-only). + * + * A type with no static lowering poisons the slot that holds it (a field, + * a parameter, a return, a local), and today the poison rejects the whole + * declaration around the slot: a class, a signature, a declaration + * statement. Every later use of that declaration then fails on its own + * line. The localized-failure design turns such slots into checked + * dynamic slots at the points where the build would otherwise commit the + * error. + * + * This module holds the bookkeeping that stage measures without changing + * any lowering: where a failing type sits (its slot), whether a dynamic + * slot would keep the program's meaning (design §3.7), and the keys that + * tie a cascade diagnostic to the rejected declaration it inherits from. + * The data rides the committed diagnostics as report-only fields + * (ScrDiagnostic.typeFailure, .rejects, .cause); tier-analysis.ts turns it + * into the would-be dynamic slot report of `--explain-dynamic`. */ +import type { + TypeFailure, + TypeFailureFamily, + TypeFailureSlot, +} from "../../diagnostics/diagnostic.js"; +import * as ts from "../ts7/adapter.js"; + +/** Families a checked-dynamic slot can hold (design §3.7). The others + * name values the compiler cannot produce (opaque stdlib and @types/node + * types, engine-backed ambient types), package value types that scope B + * routes to Node islands, enum objects with no runtime value, and generic + * implementations without an erased instance. */ +const DEGRADABLE_FAMILIES: ReadonlySet = new Set([ + "index-signature", + "overloads", + "intersection", + "component", + "record-member", + "unsupported", + "dyn-field-policy", +]); + +function nameText(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) return name.text; + if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text; + return ts.getTextOfNode(name); +} + +function isParameterProperty(param: ts.ParameterDeclaration): boolean { + return ( + ts.isConstructorDeclaration(param.parent) && + (param.modifiers?.nodes ?? []).some( + (m) => + m.kind === ts.SyntaxKind.PublicKeyword || + m.kind === ts.SyntaxKind.PrivateKeyword || + m.kind === ts.SyntaxKind.ProtectedKeyword || + m.kind === ts.SyntaxKind.ReadonlyKeyword, + ) + ); +} + +function variableSlot(decl: ts.VariableDeclaration): TypeFailureSlot { + const list = decl.parent; + const holder = list?.parent; + if ( + holder !== undefined && + (ts.isForOfStatement(holder) || ts.isForInStatement(holder) || ts.isForStatement(holder)) + ) + return "loop"; + if (holder !== undefined && ts.isVariableStatement(holder) && ts.isSourceFile(holder.parent)) + return "global"; + return "local"; +} + +function functionLabel(fn: ts.Node): string { + if (ts.isConstructorDeclaration(fn)) return "constructor"; + if (ts.isFunctionLikeDeclaration(fn)) { + const own = nameText(fn.name); + if (own !== null) return own; + const parent = fn.parent; + if ( + parent !== undefined && + (ts.isVariableDeclaration(parent) || + ts.isPropertyAssignment(parent) || + ts.isPropertyDeclaration(parent)) + ) { + const bound = nameText(parent.name); + if (bound !== null) return bound; + } + } + return ""; +} + +/** The slot a badType blame node denotes. A declaration's own node, its + * name and its type annotation name the declaration's slot; anything + * reached through an expression is an expression slot (the type of a + * cast, a literal, a temporary). */ +export function typeFailureSlot(node: ts.Node): { slot: TypeFailureSlot; name: string } { + let current: ts.Node = node; + for (let depth = 0; depth < 64; depth++) { + if (ts.isParameter(current)) { + const name = nameText(current.name) ?? ""; + return { slot: isParameterProperty(current) ? "field" : "param", name }; + } + if (ts.isPropertyDeclaration(current)) + return { slot: "field", name: nameText(current.name) ?? "" }; + if (ts.isVariableDeclaration(current)) + return { slot: variableSlot(current), name: nameText(current.name) ?? "" }; + if (ts.isBindingElement(current)) { + let owner: ts.Node | undefined = current.parent; + while (owner !== undefined && !ts.isVariableDeclaration(owner) && !ts.isParameter(owner)) + owner = owner.parent; + const slot: TypeFailureSlot = + owner === undefined + ? "local" + : ts.isParameter(owner) + ? "param" + : variableSlot(owner as ts.VariableDeclaration); + return { slot, name: nameText(current.name) ?? "" }; + } + if (ts.isPropertySignature(current) || ts.isMethodSignatureDeclaration(current)) + return { slot: "member", name: nameText(current.name) ?? "" }; + if (ts.isFunctionLikeDeclaration(current)) + return { slot: "return", name: functionLabel(current) }; + if (ts.isCatchClause(current)) return { slot: "local", name: "" }; + const parent: ts.Node | undefined = current.parent; + if (parent === undefined || ts.isSourceFile(parent)) break; + // Climb from a declaration's name or type annotation, or from inside a + // type node; any other position is an expression's own type. + const declared = + (ts.isParameter(parent) || + ts.isPropertyDeclaration(parent) || + ts.isVariableDeclaration(parent) || + ts.isBindingElement(parent) || + ts.isPropertySignature(parent) || + ts.isMethodSignatureDeclaration(parent) || + ts.isFunctionLikeDeclaration(parent)) && + ((parent as { name?: ts.Node }).name === current || + (parent as { type?: ts.Node }).type === current); + if (!declared && !ts.isTypeNode(current) && !ts.isTypeNode(parent)) break; + current = parent; + } + const text = ts.getTextOfNode(node); + return { slot: "expression", name: text.length > 60 ? `${text.slice(0, 57)}...` : text }; +} + +/** Program code outside node_modules, in TypeScript: the code holes apply + * to (design §3.3, §3.8). JavaScript keeps its dynamic fallback and its + * runtime fences; npm packages keep island routing. */ +function programTypeScript(node: ts.Node): boolean { + const sf = ts.getSourceFileOfNode(node); + const file = sf.fileName; + if (file.includes("/node_modules/")) return false; + if (sf.isDeclarationFile) return false; + return !/\.(?:[cm]?js|jsx)$/.test(file); +} + +export function typeFailureOf( + node: ts.Node, + declaredType: string, + family: TypeFailureFamily, +): TypeFailure { + const { slot, name } = typeFailureSlot(node); + return { + slot, + name, + declaredType, + family, + degradable: DEGRADABLE_FAMILIES.has(family) && programTypeScript(node), + }; +} + +function locKey(node: ts.Node): string { + const sf = ts.getSourceFileOfNode(node); + return `${sf.fileName}:${ts.tokenStartOfNode(node)}`; +} + +/** The failure-group key of a deferred declaration (a class shape or a + * function signature whose collection poisoned). */ +export function declarationGroupKey(symbol: ts.Symbol): string { + const decl = symbol.declarations[0]; + const name = ts.symbolName(symbol); + if (decl === undefined) return `declaration ${name}`; + const kind = + ts.isClassDeclaration(decl) || ts.isClassExpression(decl) + ? "class" + : ts.isFunctionLikeDeclaration(decl) + ? "function" + : "declaration"; + return `${kind} ${name} @${locKey(decl)}`; +} + +/** The failure-group key of a poisoned declaration statement whose + * bindings are blocked. */ +export function statementGroupKey(stmt: ts.Statement): string { + return `statement @${locKey(stmt)}`; +} diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts index b75e491a84..0baedd7dd7 100644 --- a/packages/compiler/src/frontend/tier-analysis.ts +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -29,7 +29,12 @@ * Attribution is syntactic and approximate: the site's innermost node, the * operand it acts on, and that operand's declaration. Stage S6 replaces it * with requests the failing lowering names exactly. */ -import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { + ScrDiagnostic, + TypeFailureFamily, + TypeFailureSlot, +} from "../diagnostics/diagnostic.js"; +import { isWarningCode } from "../diagnostics/render.js"; import type { IrFunction, SrcLoc } from "../ir/ir.js"; import { dynamicOperationSites, type DynamicFunction } from "../ir/tiers.js"; import * as ts from "./ts7/adapter.js"; @@ -85,8 +90,70 @@ export interface TierDynamicFunction extends DynamicFunction { statements: number; } +/** The localized-failure stage whose slot positions include a slot kind + * (design §3.6): locals and globals degrade first, declarations next, + * members of declared types with push-down, expression types last. */ +export type LocalizedFailureStage = "L1" | "L2" | "L3" | "L4"; + +export function slotStage(slot: TypeFailureSlot): LocalizedFailureStage { + switch (slot) { + case "local": + case "global": + case "loop": + return "L1"; + case "field": + case "param": + case "return": + return "L2"; + case "member": + return "L3"; + case "expression": + return "L4"; + } +} + +/** A committed type failure that would become a checked dynamic slot + * (SC6104) instead of an error. */ +export interface WouldBeSlot { + loc: SrcLoc; + slot: TypeFailureSlot; + name: string; + declaredType: string; + family: TypeFailureFamily; + code: string; + message: string; + stage: LocalizedFailureStage; + /** The declaration the failure rejects today (a failure-group key), if + * any: a class, a signature, or a declaration statement. */ + rejects: string | null; + /** Other committed diagnostics that inherit from this slot's group and + * clear once every group they inherit from degrades (with push-down and + * expression slots, the full design). */ + cascade: number; +} + +/** Localized failure, report-only (stage L0): what the committed errors + * would become. Two scenarios: through stage L2 (declaration slots) and + * through L4 (members and expressions too). */ +export interface LocalizedFailureReport { + /** Reached errors (warnings excluded). */ + errors: number; + /** Type failures, by whether a checked dynamic slot keeps meaning. */ + typeFailures: { degradable: number; refused: number }; + slots: WouldBeSlot[]; + /** Diagnostics attributed to a rejected declaration they used. */ + cascades: number; + /** Errors that disappear (degraded slots plus cleared cascades). */ + clearedThroughL2: number; + clearedThroughL4: number; + /** The errors that remain through L4, by code. */ + remainingByCode: Record; +} + export interface TierReport { mode: TierMode; + /** Localized failure (report-only). */ + localizedFailure: LocalizedFailureReport; /** Reached source statements that perform a dynamic operation. */ statementsDynamic: number; dynamicFunctions: TierDynamicFunction[]; @@ -576,6 +643,119 @@ function demotedUnits(requests: readonly DemotionRequest[]): DemotedUnit[] { ); } +/* ── localized failure ───────────────────────────────────────────────── */ + +/** A diagnostic's failure-group bookkeeping (lowerer: badType, deferred + * flushes, blocked statements) turned into the would-be report. A group + * (a rejected declaration or blocked statement) clears in a scenario when + * each of its diagnostics either degrades (a degradable type failure at a + * slot the scenario covers) or is a cascade whose every cause clears. */ +export function localizedFailureReport(reached: readonly ScrDiagnostic[]): LocalizedFailureReport { + const errors = reached.filter((d) => !isWarningCode(d.code)); + const groups = new Map(); + for (const d of errors) { + if (d.rejects === undefined) continue; + const list = groups.get(d.rejects); + if (list === undefined) groups.set(d.rejects, [d]); + else list.push(d); + } + const stages: Record<"L2" | "L4", ReadonlySet> = { + L2: new Set(["L1", "L2"]), + L4: new Set(["L1", "L2", "L3", "L4"]), + }; + const clears = (scenario: "L2" | "L4") => { + const covered = stages[scenario]; + const memo = new Map(); + const degrades = (d: ScrDiagnostic): boolean => + d.typeFailure !== undefined && + d.typeFailure.degradable && + covered.has(slotStage(d.typeFailure.slot)); + const causeClears = (d: ScrDiagnostic): boolean => + d.cause !== undefined && d.cause.length > 0 && d.cause.every((g) => groupClears(g)); + const groupClears = (group: string): boolean => { + const known = memo.get(group); + if (known !== undefined) return known; + memo.set(group, false); // a cycle never clears + const members = groups.get(group) ?? []; + const ok = members.length > 0 && members.every((d) => degrades(d) || causeClears(d)); + memo.set(group, ok); + return ok; + }; + return { degrades, causeClears, groupClears }; + }; + const l2 = clears("L2"); + const l4 = clears("L4"); + const cleared = (c: ReturnType, d: ScrDiagnostic) => + c.degrades(d) || c.causeClears(d); + // Each slot's cascade: diagnostics whose causes (transitively) include + // the slot's group and that clear in the full scenario. + const reachesGroup = (d: ScrDiagnostic, group: string, seen: Set): boolean => { + for (const g of d.cause ?? []) { + if (g === group) return true; + if (seen.has(g)) continue; + seen.add(g); + for (const m of groups.get(g) ?? []) if (reachesGroup(m, group, seen)) return true; + } + return false; + }; + const slots: WouldBeSlot[] = []; + let degradable = 0; + let refused = 0; + for (const d of errors) { + const f = d.typeFailure; + if (f === undefined) continue; + if (!f.degradable) { + refused++; + continue; + } + degradable++; + const group = d.rejects ?? null; + let cascade = 0; + if (group !== null) + for (const e of errors) + if ( + e !== d && + e.typeFailure === undefined && + cleared(l4, e) && + reachesGroup(e, group, new Set()) + ) + cascade++; + slots.push({ + loc: d.loc, + slot: f.slot, + name: f.name, + declaredType: f.declaredType, + family: f.family, + code: d.code, + message: d.message, + stage: slotStage(f.slot), + rejects: group, + cascade, + }); + } + slots.sort( + (a, b) => + b.cascade - a.cascade || a.loc.file.localeCompare(b.loc.file) || a.loc.start - b.loc.start, + ); + const remainingByCode: Record = {}; + let clearedThroughL2 = 0; + let clearedThroughL4 = 0; + for (const d of errors) { + if (cleared(l2, d)) clearedThroughL2++; + if (cleared(l4, d)) clearedThroughL4++; + else remainingByCode[d.code] = (remainingByCode[d.code] ?? 0) + 1; + } + return { + errors: errors.length, + typeFailures: { degradable, refused }, + slots, + cascades: errors.filter((d) => d.cause !== undefined && d.typeFailure === undefined).length, + clearedThroughL2, + clearedThroughL4, + remainingByCode, + }; +} + export interface TierAnalysisInput { mode: TierMode; /** The dynamic-tier IR functions of the reached program. */ @@ -624,6 +804,7 @@ export function analyzeTiers(program: ts.Program, input: TierAnalysisInput): Tie const dynamic = dynamicFunctionReports(program, input.dynamicFunctions); return { mode: input.mode, + localizedFailure: localizedFailureReport(input.reached), statementsDynamic: dynamic.statements, dynamicFunctions: dynamic.functions, requests, diff --git a/tests/coverage-fixtures/localized-failure.ts b/tests/coverage-fixtures/localized-failure.ts new file mode 100644 index 0000000000..ed4d30e8c7 --- /dev/null +++ b/tests/coverage-fixtures/localized-failure.ts @@ -0,0 +1,53 @@ +// The localized-failure report of `scriptc coverage --explain-dynamic`: +// type failures that would become checked dynamic slots, and the uses +// each one rejects today. +interface Tool { + name: string; + execute(id: string, params: any): Promise; +} +type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; + +// A field: rejects the class, so every use of an instance fails. +class Session { + private tools: Tool[] = []; + count(): number { + return this.tools.length; + } + label(): string { + return "s"; + } +} + +// A parameter: rejects the function, so every call fails. +function listAll(dir: string, progress?: Callbacks): number { + return dir.length; +} + +// The dyn class field policy. +class Holder { + state: any = {}; + get(): number { + return 1; + } +} + +// A local: rejects its declaration statement, so its uses fail. +function pick(n: number): boolean { + const callbacks: Callbacks | undefined = n > 1 ? new Set() : undefined; + return callbacks === undefined; +} + +// Refused: a value the compiler cannot produce stays an error. +class Cache { + tokens: WeakRef = new WeakRef({}); + size(): number { + return 0; + } +} + +const s = new Session(); +console.log(s.count(), s.label()); +console.log(listAll("abc")); +console.log(new Holder().get()); +console.log(pick(1), new Cache().size()); diff --git a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt new file mode 100644 index 0000000000..ed727b6048 --- /dev/null +++ b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt @@ -0,0 +1,49 @@ + dynamic tier (tier assignment, report-only; --tier=auto) + 0 functions already operate on dynamic values (0 statements) + + 11 demotion requests (refusals and fences the dynamic tier would replace) + + 4 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) + 11 errors: 9 clear with declaration slots (L1-L2), 9 with member and expression slots too (L3-L4); 6 cascades attributed; 1 type failure stays refused + L2 field tools: Tool[] tests/coverage-fixtures/localized-failure.ts:13:11 (SC2009 component, clears 2) + rejects class Session + L2 param progress: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:23:31 (SC2009 component, clears 1) + rejects function listAll + L2 field state: any tests/coverage-fixtures/localized-failure.ts:29:3 (SC1090 dyn-field-policy, clears 1) + rejects class Holder + L1 local callbacks: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:37:9 (SC2009 component, clears 1) + rejects statement + class Session tests/coverage-fixtures/localized-failure.ts:12:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:49:11: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + class Holder tests/coverage-fixtures/localized-failure.ts:28:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:52:13: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + class Cache tests/coverage-fixtures/localized-failure.ts:42:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:53:22: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + field Session.tools tests/coverage-fixtures/localized-failure.ts:13:3 (1 request) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:13:11: values of type 'Tool[]' cannot be compiled: the array shape is supported, but its element type 'Tool' does not compile + -> function turns dynamic + field Holder.state tests/coverage-fixtures/localized-failure.ts:29:3 (1 request) + <- SC1090 operation at tests/coverage-fixtures/localized-failure.ts:29:3: 'any'-typed class fields are not supported yet + -> function turns dynamic + field Cache.tokens tests/coverage-fixtures/localized-failure.ts:43:3 (1 request) + <- SC2020 operation at tests/coverage-fixtures/localized-failure.ts:43:3: 'WeakRef' is part of the standard library types but has no scriptc lowering yet + -> function turns dynamic + function tests/coverage-fixtures/localized-failure.ts:1:1 (8 sites inside) + function listAll tests/coverage-fixtures/localized-failure.ts:23:1 (1 request, 1 site inside) + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:51:13: uses of 'listAll' inherit the blocker on its declaration + -> function turns dynamic + function pick tests/coverage-fixtures/localized-failure.ts:36:1 (2 sites inside) + binding progress in listAll tests/coverage-fixtures/localized-failure.ts:23:31 (1 request) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:23:31: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function listAll turns dynamic + binding callbacks in pick tests/coverage-fixtures/localized-failure.ts:37:9 (2 requests) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:37:9: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function pick turns dynamic + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:38:10: uses of 'callbacks' inherit the blocker on its declaration + -> function pick turns dynamic + binding s tests/coverage-fixtures/localized-failure.ts:49:7 (1 request) + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:50:13: uses of 's' inherit the blocker on its declaration + -> function turns dynamic \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index fde16a901b..3f9a60bcea 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -428,6 +428,38 @@ test("--explain-dynamic attributes each refusal to the unit the dynamic tier wou ); }); +test("--explain-dynamic lists the type failures localized failure would turn into dynamic slots", async () => { + const { coverage } = analyze(fixture("localized-failure.ts"), { explainDynamic: true }); + const report = coverage.tiers!.localizedFailure; + expect(report.errors).toBe(11); + expect(report.typeFailures).toEqual({ degradable: 4, refused: 1 }); + expect( + report.slots.map((s) => [ + s.stage, + s.slot, + s.name, + s.family, + s.rejects?.split(" @")[0], + s.cascade, + ]), + ).toEqual([ + ["L2", "field", "tools", "component", "class Session", 2], + ["L2", "param", "progress", "component", "function listAll", 1], + ["L2", "field", "state", "dyn-field-policy", "class Holder", 1], + ["L1", "local", "callbacks", "component", "statement", 1], + ]); + // The WeakRef field stays refused, so its class and the construction + // that uses it stay errors. + expect(report.clearedThroughL2).toBe(9); + expect(report.remainingByCode).toEqual({ SC1090: 1, SC2020: 1 }); + const relative = JSON.parse( + JSON.stringify(coverage.tiers).replaceAll(repoRoot + "/", ""), + ) as typeof coverage.tiers; + await expect(renderExplainDynamic(relative!)).toMatchFileSnapshot( + "__snapshots__/coverage-explain-localized-failure.txt", + ); +}); + test("CLI: --tier and --explain-dynamic", () => { const scriptcCli = join(repoRoot, "packages/cli/src/main.ts"); const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-")); From e884e53001b0a1a79b94807eddf070699750ce34 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:53:05 -0700 Subject: [PATCH 05/49] Keep one identity through checked casts of dynamic values A checked cast of a dynamic value to a record or array (value as T) built a separate native copy: writes through the result missed the source and every other holder, and === was false, where Node's as does nothing at run time. Every such cast now runs the declaration boundary's adoption leniently (dyn.adoptShared): each source object or array something else still holds becomes, in place, a live capsule of the native value built from it. A node that is not plain data, or that the value holds twice, keeps the copy. A source nothing else can hold converts outright, which keeps the fused JSON.parse(text) as T: a fresh JSON.parse or response.json(), workerData read at one site, the payload of the program's one message listener, and a cast whose result serves one primitive member read. Data-only object and array literals in untyped slots the program declares (unknown or any variables, fields, parameters, returns, unknown[] elements), thrown literals and their members are built as dynamic objects, so their later casts adopt them too. A boxed native value cast to another native layout keeps its cached copy (object model decision D3); the harness test pins it. Ported from the fix/dyn-record-identity prototype. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/expressions/message-listeners.ts | 22 +++ .../lowering/expressions/narrowing.ts | 158 +++++++++++++++++- .../lowering/expressions/object-literals.ts | 3 +- .../src/frontend/lowering/lower-stmts.ts | 11 +- .../compiler/src/frontend/lowering/lowerer.ts | 97 +++++++++++ .../compiler/src/ir/builtin-signatures.ts | 6 + packages/compiler/src/ir/runtime-features.ts | 5 +- .../corpus/dyn-cast-identity-fresh.ts.json | 6 + .../corpus/dyn-cast-identity-holders.ts.json | 6 + .../corpus/dyn-cast-identity-workers.ts.json | 6 + .../tests/corpus/dyn-cast-identity.ts.json | 6 + .../tests/corpus/dyn-literal-identity.ts.json | 6 + packages/runtime/src/scr_decl_adopt.c | 49 ++++-- packages/runtime/src/scr_dyn.h | 1 + tests/corpus/dyn-cast-identity-fresh.ts | 21 +++ tests/corpus/dyn-cast-identity-holders.ts | 62 +++++++ tests/corpus/dyn-cast-identity-workers.ts | 23 +++ tests/corpus/dyn-cast-identity.ts | 71 ++++++++ tests/corpus/dyn-literal-identity.ts | 83 +++++++++ .../dyn-cast-identity-fresh.ts.json | 1 + .../dyn-cast-identity-holders.ts.json | 1 + .../dyn-cast-identity-workers.ts.json | 1 + .../corpus-costs/dyn-cast-identity.ts.json | 1 + .../corpus-costs/dyn-literal-identity.ts.json | 1 + tests/harness/dyncheck.test.ts | 27 +++ 26 files changed, 659 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/expressions/message-listeners.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json create mode 100644 tests/corpus/dyn-cast-identity-fresh.ts create mode 100644 tests/corpus/dyn-cast-identity-holders.ts create mode 100644 tests/corpus/dyn-cast-identity-workers.ts create mode 100644 tests/corpus/dyn-cast-identity.ts create mode 100644 tests/corpus/dyn-literal-identity.ts create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity.ts.json create mode 100644 tests/harness/corpus-costs/dyn-literal-identity.ts.json diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4b3f34b807..0bbdff1d85 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1683,6 +1683,7 @@ export const LIB_FN_SYMS: Record = { "dyn.objEntries": "scr_dyn_obj_entries", "dyn.structuredClone": "scr_structured_clone", "dyn.declAdopt": "scr_dyn_decl_adopt", + "dyn.adoptShared": "scr_dyn_adopt_shared", "dyn.cloneMissing": "scr_structured_clone_missing", "dyn.cloneTransferFail": "scr_structured_clone_transfer_fail", "dyn.cloneTransferFailWith": "scr_structured_clone_transfer_fail_with", diff --git a/packages/compiler/src/frontend/lowering/expressions/message-listeners.ts b/packages/compiler/src/frontend/lowering/expressions/message-listeners.ts new file mode 100644 index 0000000000..698bf4dc3d --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/message-listeners.ts @@ -0,0 +1,22 @@ +import * as ts from "../../ts7/adapter.js"; + +const LISTENER_METHODS = new Set([ + "on", + "once", + "addListener", + "prependListener", + "prependOnceListener", +]); + +/** `.on("message", …)` and the other listener registrations. */ +export function isMessageListenerCall(call: ts.CallExpression): boolean { + const callee = call.expression; + const event = call.arguments.nodes[0]; + return ( + ts.isPropertyAccessExpression(callee) && + LISTENER_METHODS.has(callee.name.text) && + event !== undefined && + ts.isStringLiteral(event) && + event.text === "message" + ); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f05596727..d4407538b9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -50,6 +50,7 @@ import { isConstAssertionTypeNode, unitOnlyUnion } from "../../type-mapper.js"; import { assertsGlobalObject, extractionCopiesReference } from "../assertion-identity.js"; import { iterableSlotValue, iterableUnionOf } from "../iterable-values.js"; import { isDeclBoundaryTypeNode } from "../../decl-projection.js"; +import { isMessageListenerCall } from "./message-listeners.js"; /** `c ? a : b`. An explicit destination constructs each branch at that * layout before joining it. Without one, retain the expression's inferred @@ -1575,7 +1576,7 @@ export function lowerAsExpression( // contains undefined, so casts over parse results keep failing on // non-string values with the usual path-annotated TypeError. if (target.kind === "union" && lowerer.dynConvertible(target)) { - return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; + return checkedDynCast(lowerer, expr, inner, target); } // Uint8Array targets: the checked-dynamic tree carries a bytes kind now (converted // stdin chunks) — the extraction validates the kind and copies out. @@ -1619,9 +1620,145 @@ export function lowerAsExpression( `number, string, boolean, records, arrays, and unions of those)`, ); } + return checkedDynCast(lowerer, expr, inner, target); +} + +/** The checked conversion of a dynamic value. A record or array built from + * a value another holder may still reference keeps one identity with it + * (adoptingCast); only a source nothing else can hold (freshDynSource) + * converts outright, which keeps the fused `JSON.parse(text) as T`. */ +function checkedDynCast( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, + target: IrType, +): IrExpr { + if ( + holdsReference(lowerer, target) && + !freshDynSource(lowerer, expr, inner) && + !resultOnlyRead(lowerer, expr) + ) { + return adoptingCast(lowerer, inner, target, locOf(expr), "dyn.adoptShared"); + } return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; } +/** A cast whose result only serves one read of a primitive member + * (`(value as T).name`, `(value as T[]).length`): the converted value is + * never stored, compared, written or called through, and the read yields no + * reference into it, so whether it is the source itself or a copy cannot be + * observed. */ +function resultOnlyRead(lowerer: Lowerer, expr: ts.AsExpression | ts.TypeAssertion): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const access = node.parent; + if ( + access === undefined || + !( + (ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access)) && + access.expression === node + ) + ) + return false; + const primitive = + ts.TypeFlags.StringLike | + ts.TypeFlags.NumberLike | + ts.TypeFlags.BooleanLike | + ts.TypeFlags.BigIntLike | + ts.TypeFlags.Undefined | + ts.TypeFlags.Null; + const read = lowerer.typeOf(access); + const parts = read.isUnionType() ? ts.constituentTypes(read) : [read]; + if (!parts.every((part) => (part.flags & primitive) !== 0)) return false; + let use: ts.Node = access; + while (use.parent !== undefined && ts.isParenthesizedExpression(use.parent)) use = use.parent; + const parent = use.parent; + if (parent === undefined) return true; + if (ts.isCallExpression(parent) && parent.expression === use) return false; + if ( + ts.isBinaryExpression(parent) && + parent.left === use && + ts.isAssignmentOperator(parent.operatorToken.kind) + ) + return false; + if ( + (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && + (parent.operator === ts.SyntaxKind.PlusPlusToken || + parent.operator === ts.SyntaxKind.MinusMinusToken) + ) + return false; + if (ts.isDeleteExpression(parent)) return false; + return true; +} + +/** A cast operand nothing else can hold: a value this expression creates + * (`JSON.parse(text)`, `await response.json()`), or a structured clone the + * runtime delivers once and the program reads at this site only (`workerData` + * read nowhere else; the parameter of the program's one `"message"` + * listener, read nowhere else in it). Anything else is adopted. */ +function freshDynSource( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, +): boolean { + if (inner.kind === "libCall" && inner.fn === "json.parse") return true; + if ( + inner.kind === "awaitExpr" && + inner.value.kind === "libCall" && + inner.value.fn === "fetch.responseJson" + ) + return true; + const facts = lowerer.structuredCloneReads(); + if (inner.kind === "libCall" && inner.fn === "worker.data") return facts.workerData === 1; + let operand: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if (!ts.isIdentifier(operand) || inner.kind !== "varRef" || facts.messageListeners !== 1) + return false; + const symbol = lowerer.checker.getSymbolAtLocation(operand); + const param = symbol?.valueDeclaration; + if (!param || !ts.isParameter(param) || !ts.isIdentifier(param.name)) return false; + const fn = param.parent; + if ( + !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) || + fn.parameters.nodes[0] !== param || + !isMessageListener(fn) + ) + return false; + let reads = 0; + const name = param.name.text; + const visit = (node: ts.Node): void => { + if (ts.isIdentifier(node) && node.text === name && node !== param.name) reads++; + ts.forEachChild(node, visit); + }; + ts.forEachChild(fn, visit); + return reads === 1; +} + +/** The listener argument of `.on("message", fn)` (also once, + * addListener, prependListener, prependOnceListener) registered once: at + * module level, outside any function, class or loop, so one emitter can + * never hand the same message to two registrations of it. */ +function isMessageListener(fn: ts.Node): boolean { + const call = fn.parent; + if (!call || !ts.isCallExpression(call) || call.arguments.nodes[1] !== fn) return false; + if (!isMessageListenerCall(call)) return false; + for (let p = call.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) { + if ( + ts.isFunctionLike(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) || + ts.isClassStaticBlockDeclaration(p) || + ts.isForStatement(p) || + ts.isForInStatement(p) || + ts.isForOfStatement(p) || + ts.isWhileStatement(p) || + ts.isDoStatement(p) + ) + return false; + } + return true; +} + /** True when a type holds a record or an array somewhere (values whose * identity can be observed). */ function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): boolean { @@ -1657,6 +1794,21 @@ function declBoundaryCast( if (!holdsReference(lowerer, target)) { return { kind: "dynCheck", value: inner, type: target, loc }; } + return adoptingCast(lowerer, inner, target, loc, "dyn.declAdopt"); +} + +/** The checked conversion of `inner` to `target` followed by adoption + * (`dyn.declAdopt` at the boundary, the lenient `dyn.adoptShared` + * elsewhere): every source object or array that something else still + * references becomes a live capsule of the native value built from it. */ +function adoptingCast( + lowerer: Lowerer, + inner: IrExpr, + target: IrType, + loc: SrcLoc, + fn: "dyn.declAdopt" | "dyn.adoptShared", +): IrExpr { + const boundary = fn === "dyn.declAdopt"; const source = lowerer.declareHiddenLocal("%boundarySource", DYN); const value = lowerer.declareHiddenLocal("%boundaryValue", target); return { @@ -1669,7 +1821,7 @@ function declBoundaryCast( init: { kind: "dynCheck", value: varRef(source.id, DYN, loc), - adopt: true, + ...(boundary ? { adopt: true as const } : {}), type: target, loc, }, @@ -1679,7 +1831,7 @@ function declBoundaryCast( kind: "exprStmt", expr: { kind: "libCall", - fn: "dyn.declAdopt", + fn, args: [ varRef(source.id, DYN, loc), { diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index f57e52159d..6e4ff8a33a 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -419,7 +419,8 @@ export function lowerDynObjectLiteral( : !boxValue && (ts.isObjectLiteralExpression(valueExpr) || (ts.isArrayLiteralExpression(valueExpr) && - holdsEvolvingArray(lowerer, valueExpr))) + (holdsEvolvingArray(lowerer, valueExpr) || + lowerer.untypedLiteralContext(valueExpr)))) ? lowerer.lowerExprExpecting(valueExpr, DYN) : lowerer.lowerExpr(valueExpr as ts.Expression); raw = fenceClosureProbe(lowerer, valueExpr, undefined, lowerValue) ?? lowerValue(); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 84196476a6..37e14ec0ca 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1878,7 +1878,16 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt } // Exception-representable values move into the runtime's exception // cell. `throw f()` of a void call has no value. - let value = lowerer.lowerExpr(stmt.expression); + // A thrown object or array literal has no declared type: it is built as + // a dynamic object, so the catch binding's checked casts adopt it + // (lowerer.untypedLiteralContext). + let literal: ts.Expression = stmt.expression; + while (ts.isParenthesizedExpression(literal)) literal = literal.expression; + let value = + (ts.isObjectLiteralExpression(literal) || ts.isArrayLiteralExpression(literal)) && + lowerer.untypedLiteralContext(literal) + ? lowerer.lowerExprExpecting(stmt.expression, DYN) + : lowerer.lowerExpr(stmt.expression); if (value.type.kind === "void") lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression)); // Preserve primitive reasons and readable record identity across the diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 634c361c28..7c2d32f17b 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -460,6 +460,7 @@ import { genericLocalClassBinding, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; +import { isMessageListenerCall } from "./expressions/message-listeners.js"; import type { InstantiationPath } from "./instantiation-path.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import { completeArgs, wrappedUndefined, undefinedArgFor } from "./call-arguments.js"; @@ -2477,6 +2478,69 @@ export class Lowerer { return fallback ?? null; } + /** An object or array literal in an untyped context: its contextual type + * is `unknown` or `any` from a slot the program declares (a variable, + * field, parameter, return type or `unknown[]` element; a library + * parameter typed `any` only through a type argument), it is thrown, or + * it is a member of such a literal, and it holds data only (no methods, + * accessors or function members). Its value is built as a dynamic + * object: no type the program wrote gives it a native layout, and a later + * checked cast adopts it (dyn.adoptShared), so every holder keeps one + * identity. A library parameter the declarations type `any` or `unknown` + * itself (`console.log`, `JSON.stringify`) only reads the value and keeps + * the native literal. */ + untypedLiteralContext(x: ts.Expression): boolean { + if (isJsSourceFile(ts.getSourceFileOfNode(x))) return false; + // Data only: a literal with methods, accessors or function members is an + // object with behavior, which keeps its native layout (and boxed form). + if ( + ts.isObjectLiteralExpression(x) && + x.properties.nodes.some((p) => { + if (!ts.isPropertyAssignment(p)) + return !ts.isShorthandPropertyAssignment(p) && !ts.isSpreadAssignment(p); + let init: ts.Expression = p.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + return ( + ts.isFunctionExpression(init) || ts.isArrowFunction(init) || ts.isClassExpression(init) + ); + }) + ) + return false; + let node: ts.Node = x; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) + node = node.parent; + const parent = node.parent; + if (parent === undefined) return false; + if (ts.isThrowStatement(parent)) return true; + if (ts.isPropertyAssignment(parent) && parent.initializer === node) { + const literal = parent.parent; + if (ts.isObjectLiteralExpression(literal)) return this.untypedLiteralContext(literal); + } + if (ts.isArrayLiteralExpression(parent) && this.untypedLiteralContext(parent)) return true; + const contextual = this.checker.getContextualType(x); + if (!contextual || !(contextual.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))) + return false; + if (ts.isCallExpression(parent) || ts.isNewExpression(parent)) { + const signature = this.checker.getResolvedSignature(parent); + const decl = signature?.declaration; + if (decl && this.isStdlibFile(ts.getSourceFileOfNode(decl))) { + const index = (parent.arguments?.nodes ?? []).indexOf(node as ts.Expression); + const params = (decl as { parameters?: { nodes: readonly ts.ParameterDeclaration[] } }) + .parameters?.nodes; + const param = params?.[Math.min(index, params.length - 1)]; + let annotation = param?.type; + if (annotation && ts.isArrayTypeNode(annotation)) annotation = annotation.elementType; + if ( + !annotation || + annotation.kind === ts.SyntaxKind.AnyKeyword || + annotation.kind === ts.SyntaxKind.UnknownKeyword + ) + return false; + } + } + return true; + } + /** A fresh TypeScript data literal (`{ message: "m" }`) passed to a * checked-dynamic parameter of a callee implemented in JavaScript, whose * contract lets it reshape the object: a call through an untyped value, an @@ -9858,6 +9922,38 @@ export class Lowerer { ); }; + private structuredCloneReadCounts: { workerData: number; messageListeners: number } | null = null; + + /** How often the program reads `workerData` (every expression spelling it, + * counted conservatively) and registers a `"message"` listener: a + * structured clone read at one site only has no other holder + * (narrowing.ts freshDynSource). Computed once per lowering pass. */ + structuredCloneReads(): { workerData: number; messageListeners: number } { + if (this.structuredCloneReadCounts) return this.structuredCloneReadCounts; + const counts = { workerData: 0, messageListeners: 0 }; + const visit = (node: ts.Node): void => { + if (ts.isIdentifier(node) && node.text === "workerData") { + const parent = node.parent; + const declaresName = + parent !== undefined && + (ts.isImportSpecifier(parent) || + ts.isExportSpecifier(parent) || + ((ts.isPropertyAssignment(parent) || + ts.isPropertyDeclaration(parent) || + ts.isPropertySignature(parent) || + ts.isMethodDeclaration(parent)) && + parent.name === node)); + if (!declaresName) counts.workerData++; + } else if (ts.isCallExpression(node) && isMessageListenerCall(node)) { + counts.messageListeners++; + } + ts.forEachChild(node, visit); + }; + for (const sf of this.moduleOrder) ts.forEachChild(sf, visit); + this.structuredCloneReadCounts = counts; + return counts; + } + irTypeOf(node: ts.Node): IrType { const t = this.typeOf(node); // JS inference can leave mutable fields, accessor parameters and @@ -11458,6 +11554,7 @@ export class Lowerer { !this.mapTypeOf(this.typeOf(x)) || this.excessJsContextualProperty(x, parts) || this.reshapeableContextualLiteral(x, parts) || + this.untypedLiteralContext(x) || holdsEvolvingArray(this, x) ) { return lowerDynObjectLiteral(this, x); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e230932ba4..405dc5765b 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3967,6 +3967,12 @@ function buildLibFnSignatures() { * Throws the path TypeError, changing nothing, when a converted object * is not plain data (a prototype, accessors, symbols, frozen state). */ "dyn.declAdopt": { argTypes: [DYN, DYN], result: VOID }, + /** dyn.declAdopt for every other checked cast of a dynamic value to a + * record or array (`value as T`): a shared source object or array + * becomes a live capsule of the value built from it. A node that is not + * plain data, or that the value holds twice, keeps its separate copy. + * Never throws: the conversion already succeeded. */ + "dyn.adoptShared": { argTypes: [DYN, DYN], result: VOID }, "dyn.cloneMissing": { argTypes: [], result: DYN }, /** structuredClone with a NON-EMPTY transfer array of static values: * nothing static is transferable, so the call always throws Node's diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 494c4e3e83..cd6eb55e91 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -963,7 +963,10 @@ const RUNTIME_FEATURE_RULES = { /** The declaration-typed boundary's adoption (scr_decl_adopt.c): its * casts, and closures boxed in a boundary program, which adopt their * arguments. */ - declAdopt: rule({ fns: ["dyn.declAdopt"], initial: (mod) => mod.declBoundary === true }), + declAdopt: rule({ + fns: ["dyn.declAdopt", "dyn.adoptShared"], + initial: (mod) => mod.declBoundary === true, + }), /** for await over checked-dynamic values (scr_dyn_for_await.c). */ dynForAwait: rule({ fns: ["dyn.asyncIteratorOpen", "dyn.asyncIteratorWasSync"] }), /** Buffer.prototype methods called on checked-dynamic values diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json new file mode 100644 index 0000000000..e1643bcab1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-fresh.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json new file mode 100644 index 0000000000..e1a06ecc52 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-holders.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json new file mode 100644 index 0000000000..e8ecb066e1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-workers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json new file mode 100644 index 0000000000..fdf360d215 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json new file mode 100644 index 0000000000..41fde2c815 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-literal-identity.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_decl_adopt.c b/packages/runtime/src/scr_decl_adopt.c index 7372826aba..fc1c4456c9 100644 --- a/packages/runtime/src/scr_decl_adopt.c +++ b/packages/runtime/src/scr_decl_adopt.c @@ -15,7 +15,11 @@ * value has no prototype, accessors, symbol keys or frozen state) and * collects the shared ones; it throws the path TypeError before anything * changes. Phase two converts the shared nodes in place - * (scr_dyn_decl_become, scr_json.c). */ + * (scr_dyn_decl_become, scr_json.c). + * + * Every other checked cast of a dynamic value to a record or array runs the + * same pass leniently (scr_dyn_adopt_shared): a node that cannot become a + * capsule keeps the cast's copy. */ #include "scr_runtime.h" #include @@ -31,6 +35,9 @@ typedef struct { typedef struct { ScrDeclAdopt *items; size_t len, cap; + /* A checked cast outside the boundary (scr_dyn_adopt_shared): a node + * that cannot become a capsule keeps the copy instead of throwing. */ + bool lenient; } ScrDeclAdoptList; static void scr_decl_adopt_push(ScrDeclAdoptList *list, ScrDyn *node, ScrDyn *capsule) { @@ -44,7 +51,7 @@ static void scr_decl_adopt_push(ScrDeclAdoptList *list, ScrDyn *node, ScrDyn *ca list->len++; } -static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path) { +static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path, bool lenient) { const char *want = NULL; if (n->kind == SCR_DYN_OBJ) { if (n->prototype || n->null_proto || n->v.obj.class_name) @@ -72,7 +79,7 @@ static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path) { want = "a plain array (this value is frozen, sealed or not extensible)"; } if (!want) return true; - scr_dyn_check_fail(path, want, n); + if (!lenient) scr_dyn_check_fail(path, want, n); return false; } @@ -82,6 +89,14 @@ static ScrDyn *scr_decl_capsule_ptr_holder(const ScrDeclAdoptList *list, const S return NULL; } +/* Whether the walk follows a child: plain objects and arrays. A boxed + * native record or array is already one object with its native value; a + * cast to another layout copies it (the copy keeps a cached identity per + * capsule, scr_dyn_typed_ref_cache_cast). */ +static bool scr_decl_adopt_follows(const ScrDyn *child) { + return child && (child->kind == SCR_DYN_OBJ || child->kind == SCR_DYN_ARR); +} + /* `capsule` is owned by the call: pushed or released. */ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsule, const ScrDynPath *path, unsigned depth) { @@ -90,15 +105,16 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu scr_dyn_release(capsule); return true; } - if (!scr_decl_adopt_plain(n, path)) { + if (!scr_decl_adopt_plain(n, path, list->lenient)) { scr_dyn_release(capsule); - return false; + return list->lenient; } if (n->rc > 1 && n->rc != SIZE_MAX) { ScrDyn *seen = scr_decl_capsule_ptr_holder(list, n); if (seen) { bool same = seen->v.typed_ref.ptr == capsule->v.typed_ref.ptr; scr_dyn_release(capsule); + if (!same && list->lenient) return true; if (!same) scr_dyn_check_fail(path, "a value that is not also stored elsewhere in this value (scriptc builds a separate native copy for each place)", n); return same; @@ -112,7 +128,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu if (!ops->has(ptr, i)) continue; const ScrStr *name = ops->names[i]; ScrDyn *child = scr_dyn_obj_get(n, name->data, name->len); - if (!child || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + if (!scr_decl_adopt_follows(child)) continue; ScrDynPath p = { path, name->data, 0 }; if (!scr_decl_adopt_walk(list, child, ops->get(ptr, i), &p, depth + 1)) { if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); @@ -135,7 +151,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu size_t len = n->v.arr.len < view->v.arr.len ? n->v.arr.len : view->v.arr.len; for (size_t i = 0; i < len; i++) { ScrDyn *child = n->v.arr.items[i], *element = view->v.arr.items[i]; - if (!child || !element || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + if (!element || !scr_decl_adopt_follows(child)) continue; ScrDynPath p = { path, NULL, i }; if (!scr_decl_adopt_walk(list, child, scr_dyn_retain(element), &p, depth + 1)) { scr_dyn_release(view); @@ -150,7 +166,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu const ScrDynTypedArrayOps *ops = capsule->v.typed_ref.array; for (size_t i = 0; i < n->v.arr.len; i++) { ScrDyn *child = n->v.arr.items[i]; - if (!child || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + if (!scr_decl_adopt_follows(child)) continue; ScrDynPath p = { path, NULL, i }; if (!scr_decl_adopt_walk(list, child, ops->get(ptr, (double)i), &p, depth + 1)) { if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); @@ -162,8 +178,8 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu return true; } -void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { - ScrDeclAdoptList list = { NULL, 0, 0 }; +static void scr_decl_adopt_run(ScrDyn *source, ScrDyn *capsule, bool lenient) { + ScrDeclAdoptList list = { NULL, 0, 0, lenient }; bool ok = scr_decl_adopt_walk(&list, source, scr_dyn_retain(capsule), NULL, 0); for (size_t i = 0; i < list.len; i++) { ScrDeclAdopt *item = &list.items[i]; @@ -172,3 +188,16 @@ void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { } free(list.items); } + +void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { + scr_decl_adopt_run(source, capsule, false); +} + +/* Any other checked cast of a dynamic value to a record or array + * (`value as T`): the same adoption, so a source that something else still + * holds keeps one identity with the built value. A node that is not plain + * data, or that the value holds in two places, keeps the separate copy the + * cast built (the conversion already succeeded; nothing throws here). */ +void scr_dyn_adopt_shared(ScrDyn *source, ScrDyn *capsule) { + scr_decl_adopt_run(source, capsule, true); +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 2d916a8256..eb752a52bd 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -393,6 +393,7 @@ const ScrDynTypedRecordOps *scr_dyn_typed_ref_live_record(const ScrDyn *d); * holds. Both borrowed; a node that is not plain data throws the path * TypeError before anything changes. */ void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule); +void scr_dyn_adopt_shared(ScrDyn *source, ScrDyn *capsule); /* Prototype members of a compiled class seen as `unknown`, emitted per * class by the compiler: the string names of methods, getters and setters diff --git a/tests/corpus/dyn-cast-identity-fresh.ts b/tests/corpus/dyn-cast-identity-fresh.ts new file mode 100644 index 0000000000..f0ef0fd53c --- /dev/null +++ b/tests/corpus/dyn-cast-identity-fresh.ts @@ -0,0 +1,21 @@ +// Casts of values nothing else holds yet: a parse result and a response +// body. They convert outright; the results are ordinary records. + +type Point = { x: number; y: number }; +type Doc = { title: string; points: Point[] }; + +const parsed = JSON.parse('{"title":"t","points":[{"x":1,"y":2},{"x":3,"y":4}]}') as Doc; +parsed.points[0]!.x = 10; +parsed.points.push({ x: 5, y: 6 }); +console.log("parse", parsed.title, parsed.points.length, JSON.stringify(parsed.points[0])); + +const again = JSON.parse('{"x":1,"y":2}') as Point; +const other = JSON.parse('{"x":1,"y":2}') as Point; +console.log("separate parses", again === other, again.x === other.x); + +async function main(): Promise { + const body = (await new Response('{"title":"r","points":[{"x":7,"y":8}]}').json()) as Doc; + body.points[0]!.y = 80; + console.log("response", body.title, JSON.stringify(body.points)); +} +main(); diff --git a/tests/corpus/dyn-cast-identity-holders.ts b/tests/corpus/dyn-cast-identity-holders.ts new file mode 100644 index 0000000000..55b311462f --- /dev/null +++ b/tests/corpus/dyn-cast-identity-holders.ts @@ -0,0 +1,62 @@ +// Checked casts of dynamic values held by parameters, fields, closures and +// containers keep one identity with the holder's value. + +type Point = { x: number; y: number }; + +// A parameter: the caller's value and the cast result are one object. +function asPoint(value: unknown): Point { + return value as Point; +} +const shared: unknown = JSON.parse('{"x":1,"y":2}'); +const fromParam = asPoint(shared); +fromParam.x = 5; +console.log("parameter", (shared as Point).x, asPoint(shared) === fromParam); + +// A field of a class instance. +class Holder { + data: unknown; + constructor(data: unknown) { + this.data = data; + } +} +const holder = new Holder(JSON.parse('{"x":3,"y":4}')); +const fromField = holder.data as Point; +fromField.y = 40; +console.log("field", JSON.stringify(holder.data), (holder.data as Point) === fromField); + +// A value captured by a closure. +const captured: unknown = JSON.parse('{"x":0,"y":0}'); +const bump = (): void => { + (captured as Point).x++; +}; +bump(); +bump(); +console.log("closure", (captured as Point).x); + +// Elements of an unknown[] container. +const list: unknown[] = [JSON.parse('{"x":1,"y":1}'), JSON.parse('{"x":2,"y":2}')]; +const second = list[1] as Point; +second.x = 20; +console.log("container", JSON.stringify(list), (list[1] as Point) === second); + +// A union target with a record arm. +const maybe: unknown = JSON.parse('{"x":7,"y":8}'); +const viaUnion = maybe as Point | undefined; +if (viaUnion) viaUnion.x = 70; +console.log("union", (maybe as Point).x); + +// A record whose field is an array, mutated through both casts. +type Bag = { items: number[] }; +const bagRaw: unknown = JSON.parse('{"items":[1]}'); +const bag = bagRaw as Bag; +const items = (bagRaw as { items: unknown }).items as number[]; +items.push(2); +bag.items.push(3); +console.log("record array", bag.items.join(","), items.length, bag.items === items); + +// A native value boxed as unknown and cast back to its own type. +const nativeList: number[] = [1, 2]; +const boxedList: unknown = nativeList; +const backList = boxedList as number[]; +backList.push(3); +console.log("native array", nativeList.length, backList === nativeList); diff --git a/tests/corpus/dyn-cast-identity-workers.ts b/tests/corpus/dyn-cast-identity-workers.ts new file mode 100644 index 0000000000..9ee2e42b94 --- /dev/null +++ b/tests/corpus/dyn-cast-identity-workers.ts @@ -0,0 +1,23 @@ +// Structured clones a worker receives: workerData and message payloads. +// Two casts of workerData are one object; a message payload converts once. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +type Job = { name: string; point: { x: number; y: number }; tags: string[] }; + +if (isMainThread) { + const worker = new Worker(new URL(import.meta.url), { + workerData: { name: "job", point: { x: 1, y: 2 }, tags: ["a"] }, + }); + worker.on("message", (message: unknown) => { + const reply = message as Job; + reply.tags.push("main"); + console.log("reply", reply.name, reply.point.x, reply.tags.join(",")); + }); +} else { + const job = workerData as Job; + const same = workerData as Job; + job.point.x = 10; + same.tags.push("worker"); + console.log("worker", same.point.x, job.tags.join(","), job === same); + parentPort!.postMessage(job); +} diff --git a/tests/corpus/dyn-cast-identity.ts b/tests/corpus/dyn-cast-identity.ts new file mode 100644 index 0000000000..5ac30c52f3 --- /dev/null +++ b/tests/corpus/dyn-cast-identity.ts @@ -0,0 +1,71 @@ +// A checked cast of a dynamic value to a record or array (`value as T`) +// keeps the value's identity: every cast of the same source, and the source +// itself, read and write one object. + +type Point = { x: number; y: number }; + +// Two casts of one parsed object alias each other and the source. +const raw: unknown = JSON.parse('{"x":1,"y":2}'); +const a = raw as Point; +const b = raw as Point; +a.x = 10; +b.y = 20; +console.log("aliasing", a.x, a.y, b.x, b.y); +console.log("identity", a === b, (raw as Point) === a); +console.log("source", JSON.stringify(raw)); + +// Nested objects keep their identity through the outer and inner casts. +type Outer = { name: string; inner: { v: number } }; +const nestedRaw: unknown = JSON.parse('{"name":"o","inner":{"v":1}}'); +const outer = nestedRaw as Outer; +const inner = (nestedRaw as { inner: unknown }).inner as { v: number }; +inner.v = 2; +console.log("nested", outer.inner.v, outer.inner === inner); +outer.inner.v = 3; +console.log("nested back", inner.v); + +// Arrays: pushes and element writes show through every cast. +const listRaw: unknown = JSON.parse("[1,2]"); +const first = listRaw as number[]; +const second = listRaw as number[]; +first.push(3); +second[0] = 9; +console.log("array", first.length, second.length, first[0], first === second, JSON.stringify(listRaw)); + +// Arrays of records. +type Item = { id: number; tags: string[] }; +const itemsRaw: unknown = JSON.parse('[{"id":1,"tags":["a"]},{"id":2,"tags":[]}]'); +const items = itemsRaw as Item[]; +const again = itemsRaw as Item[]; +items[0]!.tags.push("b"); +again[1]!.id = 7; +console.log("array of records", again[0]!.tags.join(","), items[1]!.id, items[0] === again[0]); + +// The source stays an ordinary object for untyped reads and writes. +const bagRaw: unknown = JSON.parse('{"y":2,"x":1,"extra":"e"}'); +const point = bagRaw as Point; +const bag = bagRaw as Record; +bag.z = 3; +console.log("added", JSON.stringify(bagRaw), Object.keys(bag).join(",")); +delete bag.extra; +console.log("deleted", JSON.stringify(bagRaw), JSON.stringify(point)); +for (const k in bag) console.log("key", k, bag[k]); +point.x = 5; +console.log("typed write", bag.x); + +// A native record boxed as unknown comes back as itself. +const native: Point = { x: 1, y: 2 }; +const boxed: unknown = native; +const unboxed = boxed as Point; +unboxed.x = 10; +console.log("native", native.x, unboxed === native); + +// A fresh parse result has no other holder. +const fresh = JSON.parse('{"x":4,"y":5}') as Point; +console.log("fresh", fresh.x + fresh.y); + +// A frozen source keeps working as a copy. +const frozen: unknown = Object.freeze(JSON.parse('{"x":1,"y":2}')); +const held = [frozen]; +const thawed = frozen as Point; +console.log("frozen", thawed.x, held.length, Object.isFrozen(frozen)); diff --git a/tests/corpus/dyn-literal-identity.ts b/tests/corpus/dyn-literal-identity.ts new file mode 100644 index 0000000000..915b8f73eb --- /dev/null +++ b/tests/corpus/dyn-literal-identity.ts @@ -0,0 +1,83 @@ +// Object and array literals in untyped contexts (an `unknown` or `any` +// slot, an `unknown[]` element, a thrown value, an `unknown`/`any` +// parameter) keep one identity through later checked casts. + +type Counter = { count: number }; +type Item = { name: string; tags: string[] }; + +// The original example: a write through the cast shows through the holder. +const holder: any = { count: 1 }; +const item = holder as Counter; +item.count++; +console.log("any holder", holder.count, item === holder); + +const box: unknown = { count: 10 }; +const viewA = box as Counter; +const viewB = box as Counter; +viewA.count += 5; +console.log("unknown holder", viewB.count, viewA === viewB, JSON.stringify(box)); + +// Elements of an unknown[] array literal. +const values: unknown[] = [ + { name: "a", tags: ["x"] }, + { name: "b", tags: [] }, +]; +const second = values[1] as Item; +second.tags.push("y"); +console.log("unknown[] element", JSON.stringify(values), (values[1] as Item) === second); + +// Nested literals inside an untyped literal. +const tree: unknown = { name: "root", children: [{ name: "leaf", tags: ["t"] }] }; +const root = tree as { name: string; children: Item[] }; +const leaf = (tree as { children: unknown[] }).children[0] as Item; +leaf.tags.push("u"); +console.log("nested", root.children[0]!.tags.join(","), root.children[0] === leaf); + +// A thrown literal. +try { + throw { code: "E_BUSY", attempts: [1] }; +} catch (error) { + const failure = error as { code: string; attempts: number[] }; + failure.attempts.push(2); + console.log("thrown", failure.code, (error as { attempts: number[] }).attempts.length, failure === error); +} + +// Literals passed to unknown and any parameters. +function remember(value: unknown): Counter { + const counter = value as Counter; + counter.count *= 2; + return value as Counter; +} +console.log("unknown parameter", remember({ count: 4 }).count); + +function relabel(value: any): Item { + const target = value as Item; + target.name = target.name.toUpperCase(); + return value; +} +const relabeled = relabel({ name: "pkg", tags: [] }); +console.log("any parameter", relabeled.name, relabeled.tags.length); + +// A literal returned as unknown, and one stored in an unknown field. +function make(): unknown { + return { count: 0 }; +} +const made = make(); +(made as Counter).count = 7; +console.log("returned", (made as Counter).count); + +class Slot { + value: unknown = { name: "slot", tags: ["s"] }; +} +const slot = new Slot(); +(slot.value as Item).tags.push("t"); +console.log("field", JSON.stringify(slot.value)); + +// Untyped literals keep JavaScript's own key order and extra keys. +const extra: unknown = { name: "e", z: 1, tags: [], a: 2 }; +const typed = extra as Item; +typed.tags.push("k"); +console.log("extra keys", JSON.stringify(extra), Object.keys(typed).join(",")); + +// Literals handed to library calls are unchanged. +console.log(JSON.stringify({ count: 3 }), [{ n: 1 }].length); diff --git a/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json new file mode 100644 index 0000000000..8c0e66c3c2 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json new file mode 100644 index 0000000000..8c0e66c3c2 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json new file mode 100644 index 0000000000..8c0e66c3c2 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity.ts.json b/tests/harness/corpus-costs/dyn-cast-identity.ts.json new file mode 100644 index 0000000000..8c0e66c3c2 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-literal-identity.ts.json b/tests/harness/corpus-costs/dyn-literal-identity.ts.json new file mode 100644 index 0000000000..8c0e66c3c2 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-literal-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index e1a869048c..57bf9ecf9f 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -143,6 +143,33 @@ describe(`dynamic-boundary checks (scriptc-only${sanitize ? ", sanitized" : ""}) ).toBe(""); }); + test("a boxed native value cast to another layout is copied (decision D3)", async () => { + // Node's `as` is a no-op, so these writes would show through every + // holder. scriptc copies a boxed native value into a cast's different + // layout for now (the object model's decision D3: copy, throw, or + // share storage); the copy keeps one identity per boxed value. + const r = await compileAndRun( + "native-layout-casts", + `type Point = { x: number; y: number }; +const dict: Record = { x: 1, y: 2 }; +const v: unknown = dict; +const p = v as Point; +p.x = 5; +console.log(dict.x, p.x, (v as Point) === p); +const nums: number[] = [1, 2]; +const u: unknown = nums; +console.log((u as number[]) === nums); +const wide = u as (number | string)[]; +wide.push("s"); +console.log(nums.length, wide.length); +console.log(JSON.stringify({ ...(v as Record) })); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe(["1 5 true", "true", "2 3", '{"x":1,"y":2}', ""].join("\n")); + expect(r.stderr).toBe(""); + }); + test("wrong-typed field throws with the path", async () => { const r = await compileAndRun( "wrong-type", From 1ea8a6c4670dd776adaa457529a7d9cc4a17fb59 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 19:36:55 -0500 Subject: [PATCH 06/49] Skip the element read for array callbacks that never use it A callback that declares no element parameter, or binds it to a name that appears nowhere else in the function (`filter(() => true)`, `map((_, i) => i)`, `reduce((s, v) => s + 1, 0)`), no longer gets the element read and converted for each visit. The helpers and the fused loops pass a constant of the parameter's type in its place (NaN, "", false, or the undefined arm of a union that has one); other parameter types keep the read. An array holding an element its type cannot hold, written through an unknown[] view, threw a TypeError at that element for every callback method. With such callbacks it now behaves as in Node: - map, flatMap, forEach, some, every, findIndex, findLastIndex, reduce and reduceRight visit it without reading it; - filter appends each kept element as it is stored, so the written value stays in the result as the view's push would store it; - find and findLast read the element only once the predicate holds. Callbacks that read the element keep the TypeError for a value their parameter type cannot hold. --- .../src/frontend/lowering/array-values.ts | 89 ++++++ .../lowering/containers/array-callbacks.ts | 281 ++++++++++++------ .../lowering/containers/array-pipelines.ts | 45 ++- .../lowering/containers/callback-arguments.ts | 33 +- .../8014-foreign-element-callbacks.ts.json | 6 + .../corpus/8014-foreign-element-callbacks.ts | 60 ++++ .../8014-foreign-element-callbacks.ts.json | 1 + 7 files changed, 411 insertions(+), 104 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8014-foreign-element-callbacks.ts.json create mode 100644 tests/corpus/8014-foreign-element-callbacks.ts create mode 100644 tests/harness/corpus-costs/8014-foreign-element-callbacks.ts.json diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index ed52b8da5c..a35cd2179d 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -3,6 +3,7 @@ import { InternalCompilerError } from "../../errors.js"; import { dynUndefinedExpr, helperLoc, varRef } from "../../ir/build.js"; import { BOOL, + DYN, F64, type IrExpr, type IrStmt, @@ -446,6 +447,94 @@ export function appendElement( ]; } +/** The argument a helper passes in place of an element its callback never + * reads (lowerArrayCallback's `unread`): a constant of the parameter's + * type, so the slot stays unread and an element the type cannot hold + * (stored through a dynamic view) passes. Null for a type without one. */ +export function unreadArgument(lowerer: Lowerer, type: IrType, loc: SrcLoc): IrExpr | null { + if (type.kind === "f64") return { kind: "numLit", value: NaN, type: F64, loc }; + if (type.kind === "bool") return { kind: "boolLit", value: false, type: BOOL, loc }; + if (type.kind === "string") return { kind: "strLit", value: "", type: STRING, loc }; + if (type.kind === "union" && lowerer.armTag(type.unionId, UNDEFINED_T) >= 0) + return lowerer.wrappedUndefined(type, loc) ?? null; + return null; +} + +/** Append the element at `arr[index]` to `out`, an array of the same + * element type, as it is stored: a value, a present undefined, or an + * element outside the element type (stored through a dynamic view), which + * `out` keeps as one, as the view's push stores it. `out` is a binding, + * and `value` names a local of the read's type (arrayValueType) that holds + * a stored value on its way. */ +export function arraySlotAppend( + lowerer: Lowerer, + out: IrExpr, + arr: IrExpr, + index: IrExpr, + elem: IrType, + value: string, + loc: SrcLoc, +): IrStmt { + const valueT = arrayValueType(lowerer, elem); + const end: IrExpr = { + kind: "arrIntrinsic", + method: "length", + receiver: out, + args: [], + type: F64, + loc, + }; + const plain: IrStmt = { + kind: "block", + body: [ + { + kind: "varDecl", + localId: value, + init: arrayValueRead(lowerer, arr, index, elem, loc), + loc, + }, + arrayValueStore( + lowerer, + out, + end, + { kind: "varRef", localId: value, type: valueT, loc }, + elem, + loc, + ), + ], + loc, + }; + if (!elementMayBeForeign(lowerer, elem)) return plain; + return { + kind: "if", + cond: { + kind: "bin", + op: "===", + left: { kind: "arrayState", arr, index, type: F64, loc }, + right: { kind: "numLit", value: 3, type: F64, loc }, + type: BOOL, + loc, + }, + then: [ + { + kind: "exprStmt", + expr: { + kind: "dynInvoke", + recv: { kind: "dynFrom", value: out, liveRef: true, type: DYN, loc }, + method: "push", + calleeName: "push", + args: [{ kind: "libCall", fn: "array.foreign", args: [arr, index], type: DYN, loc }], + type: DYN, + loc, + }, + loc, + }, + ], + else_: [plain], + loc, + }; +} + /** Store a stabilized value without changing the array's element ABI. */ export function arrayValueStore( lowerer: Lowerer, diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 2c4d5d9b41..77292389d8 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -38,6 +38,7 @@ import { } from "../lower-array-sort.js"; import { arrayIndexPresent, + arraySlotAppend, arrayValueRead, arrayValueStore, arrayValueType, @@ -45,6 +46,7 @@ import { elementMayBeForeign, foreignElementThrow, primitiveElement, + unreadArgument, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -101,9 +103,9 @@ export function lowerArrayHofCall( } } const callback = booleanFilter - ? { fnArg: truthyFilterCallback(lowerer, valueT, loc), arity: 1 } + ? { fnArg: truthyFilterCallback(lowerer, valueT, loc), arity: 1, unread: false } : lowerArrayCallback(lowerer, argNode, [valueT], arrayOf(elem)); - const { fnArg, arity } = callback; + const { fnArg, arity, unread } = callback; const fnRet = fnArg.type.ret; if (method === "map" && (fnRet.kind === "void" || fnRet.kind === "func")) { // The result array U[] is unrepresentable here (no void elements; @@ -133,8 +135,9 @@ export function lowerArrayHofCall( : null; const outElem = filtered !== null ? filtered.elem : callbackArrayElement(lowerer, call, fnRet); // A filter that re-tags its elements keeps the single union-typed visit. + // A callback that never reads its element has no use for a typed one. const fast = - present || booleanFilter || (method === "filter" && !typeEquals(outElem, elem)) + present || booleanFilter || unread || (method === "filter" && !typeEquals(outElem, elem)) ? null : lowerFastCallback(lowerer, argNode, [valueT], 0, elem, arrayOf(elem), callback); const helper = arrayHofHelper( @@ -148,6 +151,7 @@ export function lowerArrayHofCall( callbackSite(fast ?? fnArg), fast?.type ?? null, present, + unread, ); const resultType: IrType = method === "map" || method === "filter" ? arrayOf(outElem) : VOID; const result: IrExpr = { @@ -541,7 +545,12 @@ function callbackValueRead( elem: IrType, present: boolean, loc: SrcLoc, + unread = false, ): IrExpr { + if (unread) { + const placeholder = unreadArgument(lowerer, callbackValueType(lowerer, elem, present), loc); + if (placeholder) return placeholder; + } return present ? { kind: "arrayGet", arr, index, type: elem, loc } : arrayValueRead(lowerer, arr, index, elem, loc); @@ -562,13 +571,23 @@ function arrayHofHelper( site = "", fastT: CallbackType | null = null, present = false, + unread = false, ): string { - const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}:${typeKey(outElem)}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`; + const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}:${typeKey(outElem)}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, `%arr.${method}`, key); lowerer.arrHofHelpers.set(key, name); - lowerer.arrHofInfo.set(name, { method, elem, fnRet, arity, outElem, present, split: !!fastT }); + lowerer.arrHofInfo.set(name, { + method, + elem, + fnRet, + arity, + outElem, + present, + split: !!fastT, + unread, + }); const fn = buildArrayHofFn( lowerer, name, @@ -580,6 +599,7 @@ function arrayHofHelper( outElem, fastT, present, + unread, ); lowerer.liftedFns.push(fastT ? withFastCallback(fn, fastT, elem) : fn); return name; @@ -794,6 +814,7 @@ function buildArrayHofFn( outElem: IrType, fastT: CallbackType | null = null, present = false, + unread = false, ): IrFunction { const arrT = arrayOf(elem); const valueT = callbackValueType(lowerer, elem, present); @@ -809,6 +830,7 @@ function buildArrayHofFn( elem, present, loc, + unread, ); const hasElem: IrExpr = { kind: "arrayHas", @@ -994,6 +1016,7 @@ function buildArrayHofFn( ); returnType = outT; const retained = filterRetainedValue(lowerer, varRef("v.0", valueT, loc), elem, outElem, loc); + const copied = (arity === 0 || unread) && !present && typeEquals(outElem, elem); body = [ { kind: "varDecl", @@ -1006,34 +1029,57 @@ function buildArrayHofFn( { kind: "if", cond: hasElem, - then: [ - { kind: "varDecl", localId: "v.0", init: getElem, loc }, - { - kind: "if", - // ToBoolean over the predicate's answer — inert when it already - // returned bool, the per-union helper when it returned a union. - cond: filterCond(callF(varRef("v.0", valueT, loc)), fnRet, loc), - then: [ - arrayValueStore( - lowerer, - varRef("out.0", outT, loc), - { - kind: "arrIntrinsic", - method: "length", - receiver: varRef("out.0", outT, loc), - args: [], - type: F64, - loc, - }, - retained, - outElem, + // A predicate that never reads the element leaves the slot unread: + // a kept element is appended as it is stored (arraySlotAppend), so + // one the element type cannot hold stays in the result. + then: copied + ? [ + { + kind: "if", + cond: filterCond(callF(getElem), fnRet, loc), + then: [ + arraySlotAppend( + lowerer, + varRef("out.0", outT, loc), + varRef("a.0", arrT, loc), + varRef("i.0", F64, loc), + elem, + "v.0", + loc, + ), + ], + else_: null, loc, - ), + }, + ] + : [ + { kind: "varDecl", localId: "v.0", init: getElem, loc }, + { + kind: "if", + // ToBoolean over the predicate's answer — inert when it already + // returned bool, the per-union helper when it returned a union. + cond: filterCond(callF(varRef("v.0", valueT, loc)), fnRet, loc), + then: [ + arrayValueStore( + lowerer, + varRef("out.0", outT, loc), + { + kind: "arrIntrinsic", + method: "length", + receiver: varRef("out.0", outT, loc), + args: [], + type: F64, + loc, + }, + retained, + outElem, + loc, + ), + ], + else_: null, + loc, + }, ], - else_: null, - loc, - }, - ], else_: null, loc, }, @@ -1088,7 +1134,7 @@ export function lowerArrayFindLikeCall( presentElements(lowerer, call, elem) && ((method !== "find" && method !== "findLast") || (!bindUntyped && lowerer.coercibleValue(elem, lowerer.irTypeOf(call)))); - const { fnArg, arity } = lowerArrayCallback( + const { fnArg, arity, unread } = lowerArrayCallback( lowerer, argNode, [callbackValueType(lowerer, elem, present)], @@ -1109,18 +1155,19 @@ export function lowerArrayFindLikeCall( method === "findIndex" || method === "findLastIndex" ) { - const fast = present - ? null - : lowerFastCallback( - lowerer, - argNode, - [arrayValueType(lowerer, elem)], - 0, - elem, - arrT, - { fnArg, arity }, - bindUntyped, - ); + const fast = + present || unread + ? null + : lowerFastCallback( + lowerer, + argNode, + [arrayValueType(lowerer, elem)], + 0, + elem, + arrT, + { fnArg, arity }, + bindUntyped, + ); const fastT = fast?.type ?? null; const helper = method === "findIndex" || method === "findLastIndex" @@ -1134,6 +1181,7 @@ export function lowerArrayFindLikeCall( callbackSite(fast ?? fnArg), fastT, present, + unread, ) : someEveryHelper( lowerer, @@ -1145,6 +1193,7 @@ export function lowerArrayFindLikeCall( callbackSite(fast ?? fnArg), fastT, present, + unread, ); return { kind: "call", @@ -1182,6 +1231,7 @@ export function lowerArrayFindLikeCall( retag, callbackSite(fnArg), present, + unread, ); return { kind: "call", callee: helper, args: [receiver, fnArg], type: resultT, loc }; } @@ -1275,9 +1325,10 @@ function findHelper( retag: string | null, site = "", present = false, + unread = false, ): string { const method = last ? "findLast" : "find"; - const key = `${method}:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}${site}${present ? ":present" : ""}`; + const key = `${method}:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}${site}${present ? ":present" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, `%arr.${method}`, key); @@ -1296,28 +1347,45 @@ function findHelper( type: resultT, loc, }; - const visitBody: IrStmt[] = [ - { - kind: "varDecl", - localId: "v.0", - init: callbackValueRead( - lowerer, - varRef("a.0", arrT, loc), - varRef("i.0", F64, loc), - elem, - present, - loc, - ), + const read: IrStmt = { + kind: "varDecl", + localId: "v.0", + init: callbackValueRead( + lowerer, + varRef("a.0", arrT, loc), + varRef("i.0", F64, loc), + elem, + present, loc, - }, - { - kind: "if", - cond: predicateToBoolean(callArrayCallback(arrT, fnT, fnRet, arity, v, loc)), - then: [{ kind: "return", value: found, loc }], - else_: null, - loc, - }, - ]; + ), + loc, + }; + // A predicate that takes no element, or never reads it, reads it only + // once it holds: the element of a visit it rejects stays unread. + const placeholder = unread ? unreadArgument(lowerer, valueT, loc) : null; + const visitBody: IrStmt[] = + arity === 0 || placeholder + ? [ + { + kind: "if", + cond: predicateToBoolean( + callArrayCallback(arrT, fnT, fnRet, arity, placeholder ?? v, loc), + ), + then: [read, { kind: "return", value: found, loc }], + else_: null, + loc, + }, + ] + : [ + read, + { + kind: "if", + cond: predicateToBoolean(callArrayCallback(arrT, fnT, fnRet, arity, v, loc)), + then: [{ kind: "return", value: found, loc }], + else_: null, + loc, + }, + ]; const visit: IrStmt[] = visitBody; const loop = last ? reverseArrayLoop(loc, visit) @@ -1375,9 +1443,10 @@ function findIndexHelper( site = "", fastT: CallbackType | null = null, present = false, + unread = false, ): string { const method = last ? "findLastIndex" : "findIndex"; - const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`; + const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, `%arr.${method}`, key); @@ -1391,6 +1460,7 @@ function findIndexHelper( outElem: elem, present, split: !!fastT, + unread, }); const arrT = arrayOf(elem); const fnT = funcOf([callbackValueType(lowerer, elem, present), F64, arrT].slice(0, arity), fnRet); @@ -1411,6 +1481,7 @@ function findIndexHelper( elem, present, loc, + unread, ), loc, ), @@ -1460,8 +1531,9 @@ function someEveryHelper( site = "", fastT: CallbackType | null = null, present = false, + unread = false, ): string { - const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`; + const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, `%arr.${method}`, key); @@ -1474,6 +1546,7 @@ function someEveryHelper( outElem: elem, present, split: !!fastT, + unread, }); const arrT = arrayOf(elem); const fnT = funcOf([callbackValueType(lowerer, elem, present), F64, arrT].slice(0, arity), fnRet); @@ -1490,6 +1563,7 @@ function someEveryHelper( elem, present, loc, + unread, ), loc, ), @@ -1553,7 +1627,7 @@ export function lowerArrayFlatMapCall( const argNode = call.arguments.nodes[0]; if (!argNode) lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded const present = presentElements(lowerer, call, elem); - const { fnArg, arity } = lowerArrayCallback( + const { fnArg, arity, unread } = lowerArrayCallback( lowerer, argNode, [callbackValueType(lowerer, elem, present)], @@ -1603,12 +1677,13 @@ export function lowerArrayFlatMapCall( if (fnRet.kind === "void" || fnRet.kind === "func") lowerer.badType(call, lowerer.typeOf(call)); requireProducedArrayElement(lowerer, call, "'.flatMap()'", fnRet); const outElem = callbackArrayElement(lowerer, call, fnRet); - const fast = present - ? null - : lowerFastCallback(lowerer, argNode, [arrayValueType(lowerer, elem)], 0, elem, arrT, { - fnArg, - arity, - }); + const fast = + present || unread + ? null + : lowerFastCallback(lowerer, argNode, [arrayValueType(lowerer, elem)], 0, elem, arrT, { + fnArg, + arity, + }); const helper = arrayHofHelper( lowerer, "flatMap", @@ -1620,6 +1695,7 @@ export function lowerArrayFlatMapCall( callbackSite(fast ?? fnArg), fast?.type ?? null, present, + unread, ); return { kind: "call", @@ -1629,12 +1705,13 @@ export function lowerArrayFlatMapCall( loc, }; } - const fast = present - ? null - : lowerFastCallback(lowerer, argNode, [arrayValueType(lowerer, elem)], 0, elem, arrT, { - fnArg, - arity, - }); + const fast = + present || unread + ? null + : lowerFastCallback(lowerer, argNode, [arrayValueType(lowerer, elem)], 0, elem, arrT, { + fnArg, + arity, + }); const helper = flatMapHelper( lowerer, elem, @@ -1644,6 +1721,7 @@ export function lowerArrayFlatMapCall( callbackSite(fast ?? fnArg), fast?.type ?? null, present, + unread, ); return { kind: "call", @@ -1943,8 +2021,9 @@ function flatMapHelper( site = "", fastT: CallbackType | null = null, present = false, + unread = false, ): string { - const key = `flatMap:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`; + const key = `flatMap:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, "%arr.flatMap", key); @@ -1957,6 +2036,7 @@ function flatMapHelper( outElem: fnRet.elem, present, split: !!fastT, + unread, }); const arrT = arrayOf(elem); const inner = fnRet.elem; @@ -2053,6 +2133,7 @@ function flatMapHelper( elem, present, loc, + unread, ), loc, ), @@ -2125,7 +2206,7 @@ export function lowerArrayReduceCall( lowerer.runtimeOptionalReduceTypes.get(call) ?? (hasInit ? lowerer.irTypeOf(call) : arrayValueType(lowerer, elem)); if (accT.kind === "void" || accT.kind === "func") lowerer.badType(call, lowerer.typeOf(call)); - const { fnArg, arity } = lowerArrayCallback( + const { fnArg, arity, unread } = lowerArrayCallback( lowerer, argNode, [accT, arrayValueType(lowerer, elem)], @@ -2133,17 +2214,19 @@ export function lowerArrayReduceCall( false, accT, ); - const fast = lowerFastCallback( - lowerer, - argNode, - [accT, arrayValueType(lowerer, elem)], - 1, - elem, - arrT, - { fnArg, arity }, - false, - accT, - ); + const fast = unread + ? null + : lowerFastCallback( + lowerer, + argNode, + [accT, arrayValueType(lowerer, elem)], + 1, + elem, + arrT, + { fnArg, arity }, + false, + accT, + ); const helper = reduceHelper( lowerer, method, @@ -2155,6 +2238,7 @@ export function lowerArrayReduceCall( loc, callbackSite(fast ?? fnArg), fast?.type ?? null, + unread, ); const args: IrExpr[] = [receiver, fnArg]; if (hasInit) args.push(lowerer.lowerExprExpecting(call.arguments.nodes[1]!, accT)); @@ -2185,8 +2269,9 @@ function reduceHelper( loc: SrcLoc, site = "", fastT: CallbackType | null = null, + unread = false, ): string { - const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : "seed"}${site}${fastT ? ":split" : ""}`; + const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : "seed"}${site}${fastT ? ":split" : ""}${unread ? ":unread" : ""}`; const existing = lowerer.arrHofHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, `%arr.${method}`, key); @@ -2201,6 +2286,7 @@ function reduceHelper( present: false, split: !!fastT, hasInit, + unread, }); const arrT = arrayOf(elem); const fnT = funcOf([accT, arrayValueType(lowerer, elem), F64, arrT].slice(0, arity), fnRet); @@ -2210,6 +2296,9 @@ function reduceHelper( const right = method === "reduceRight"; const at = (index: IrExpr): IrExpr => arrayValueRead(lowerer, varRef("a.0", arrT, loc), index, elem, loc); + // The visited element, or a placeholder for a callback that never reads it. + const placeholder = unread ? unreadArgument(lowerer, arrayValueType(lowerer, elem), loc) : null; + const visited = (): IrExpr => placeholder ?? at(i); const locals: IrLocal[] = [ { id: "a.0", name: "a", type: arrT, mutable: true }, { id: "f.0", name: "f", type: fnT, mutable: true }, @@ -2320,7 +2409,7 @@ function reduceHelper( { kind: "callValue", callee: varRef("f.0", fnT, loc), - args: [varRef("acc.0", accT, loc), at(i), i, varRef("a.0", arrT, loc)].slice( + args: [varRef("acc.0", accT, loc), visited(), i, varRef("a.0", arrT, loc)].slice( 0, arity, ), diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 6307d871f4..0e64f51920 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -32,9 +32,11 @@ import { import { arrayIndexPresent, arrayValueRead, + arraySlotAppend, arrayValueStore, arrayValueType, elementMayBeForeign, + unreadArgument, } from "../array-values.js"; import type { Lowerer } from "../lowerer.js"; @@ -62,6 +64,9 @@ export interface ArrayHofInfo { present: boolean; split: boolean; hasInit?: boolean; + /** The callback never reads the element it declares + * (lowerArrayCallback's `unread`). */ + unread?: boolean; } type FuncType = IrType & { kind: "func" }; @@ -1595,13 +1600,21 @@ class FusedLoop { stage: Stage, k: number, lead: IrExpr[], - flow: Flow & { kind: "value" }, + flow: Flow, ): { pre: IrStmt[]; value: IrExpr } | null => { const arity = stage.info.arity; - const value = varRef(flow.id, flow.type, loc); const at = lead.length; const pick = (): { arg: IrExpr & { type: FuncType }; value: IrExpr | null } | null => { if (arity <= at) return { arg: stage.slow, value: null }; + // A callback that reads the value reaches it read (`read`); one + // that never reads it takes a placeholder for the unread slot. + if (flow.kind !== "value") { + const placeholder = stage.info.unread + ? unreadArgument(this.lowerer, stage.slow.type.params[at]!, loc) + : null; + return placeholder ? { arg: stage.slow, value: placeholder } : null; + } + const value = varRef(flow.id, flow.type, loc); const fast = stage.fast; if (fast && typeEquals(fast.type.params[at]!, flow.type)) return { arg: fast, value }; const paramT = stage.slow.type.params[at]!; @@ -1630,6 +1643,12 @@ class FusedLoop { return { pre: [...before, ...called.pre], value: called.value }; }; + /** The stage's callback never reads the element at parameter `at`, and + * a placeholder of its type stands for it (unreadArgument). */ + const unreadSlot = (stage: Stage, at: number): boolean => + stage.info.unread === true && + unreadArgument(this.lowerer, stage.slow.type.params[at]!, loc) !== null; + const fail = (): IrStmt[] => { this.failed = true; return []; @@ -1694,7 +1713,12 @@ class FusedLoop { if (k === chain.stages.length) return finish(flow); const stage = chain.stages[k]!; const info = stage.info; - if (flow.kind === "slot") return read(stage, (value) => visit(k, value)); + // A callback that declares no element parameter, or never reads the + // one it declares, leaves the slot unread, so an element outside the + // element type passes it as it is (a kept one is copied by the next + // stage or the collecting terminal). + if (flow.kind === "slot" && info.arity > 0 && !unreadSlot(stage, 0)) + return read(stage, (value) => visit(k, value)); const called = deliver(stage, k, [], flow); if (!called) return fail(); const { pre, value: result } = called; @@ -1791,11 +1815,20 @@ class FusedLoop { } }; + /** Append the source slot to the collected result unread: a filter + * whose callback never read the element keeps it as it is stored + * (arraySlotAppend). */ + const copySlot = (): IrStmt[] => { + const outT = resultT as IrType & { kind: "array" }; + if (holes || !typeEquals(outT.elem, arrT.elem)) return fail(); + const v = this.local("v", arrayValueType(this.lowerer, arrT.elem)); + return [arraySlotAppend(this.lowerer, varRef(out!, resultT, loc), a, i, arrT.elem, v, loc)]; + }; /** The terminal's action for one value. */ const finish = (flow: Flow): IrStmt[] => { if (terminal.kind === "length") return [increment(count!, loc)]; if (terminal.kind === "collect") { - if (flow.kind !== "value") return fail(); + if (flow.kind !== "value") return copySlot(); const last = chain.stages[chain.stages.length - 1]!; const outT = resultT as IrType & { kind: "array" }; if (!typeEquals(outT.elem, last.info.outElem)) return fail(); @@ -1816,7 +1849,9 @@ class FusedLoop { } const stage = terminal.stage; const method = stage.info.method; - if (flow.kind === "slot") return read(stage, (value) => finish(value)); + const lead = method === "reduce" ? 1 : 0; + if (flow.kind === "slot" && stage.info.arity > lead && !unreadSlot(stage, lead)) + return read(stage, (value) => finish(value)); if (method === "reduce") { const accT = stage.info.outElem; const called = deliver(stage, chain.stages.length, [varRef(acc!, accT, loc)], flow); diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index de027e08c5..3b504861a4 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -53,7 +53,7 @@ export function lowerArrayCallback( arrT: IrType, bindUntyped = false, expectedReturn?: IrType, -): { fnArg: IrExpr & { type: IrType & { kind: "func" } }; arity: number } { +): { fnArg: IrExpr & { type: IrType & { kind: "func" } }; arity: number; unread: boolean } { // `xs.map(((x) => x))`: a parenthesized callback is the function itself. while (ts.isParenthesizedExpression(argNode)) argNode = argNode.expression; // The ambient `Boolean` as the callback (`flags.every(Boolean)`, @@ -72,7 +72,11 @@ export function lowerArrayCallback( if (valueT === undefined || valueT.kind === "void" || isUnitType(valueT)) lowerer.badType(argNode, lowerer.typeOf(argNode)); if (valueT.kind === "union") lowerer.requireTruthyUnion(valueT.unionId, argNode); - return { fnArg: truthyFilterCallback(lowerer, valueT, locOf(argNode)), arity: 1 }; + return { + fnArg: truthyFilterCallback(lowerer, valueT, locOf(argNode)), + arity: 1, + unread: false, + }; } // The ambient `String` (`values.map(String)`): String(value) converts its // first argument as a direct call does, whatever the element type; the @@ -86,7 +90,7 @@ export function lowerArrayCallback( const valueT = lead[0]; if (valueT === undefined || valueT.kind === "void") lowerer.badType(argNode, lowerer.typeOf(argNode)); - return { fnArg: stringConversionCallback(lowerer, valueT, argNode), arity: 1 }; + return { fnArg: stringConversionCallback(lowerer, valueT, argNode), arity: 1, unread: false }; } const full = [...lead, F64, arrT]; // A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`): @@ -276,9 +280,32 @@ export function lowerArrayCallback( return { fnArg: fnArg as IrExpr & { type: IrType & { kind: "func" } }, arity: fnArg.type.params.length, + unread: !readsParameter(argNode, lead.length - 1), }; } +/** Whether a callback can observe its parameter at `index`: false for a + * function or arrow expression that declares no such parameter, or binds + * it to a plain name that appears nowhere else in it (any identifier of + * that name counts, including shorthand properties and shadowing + * bindings). Any other callback may. */ +function readsParameter(argNode: ts.Expression, index: number): boolean { + if (!ts.isArrowFunction(argNode) && !ts.isFunctionExpression(argNode)) return true; + const param = argNode.parameters.nodes[index]; + if (!param) return false; + if (!ts.isIdentifier(param.name) || param.initializer || param.dotDotDotToken) return true; + const name = param.name.text; + const mentions = (node: ts.Node): boolean => + (node !== param.name && + ts.isIdentifier(node) && + (node.text === name || node.text === "arguments")) || + (ts.forEachChild(node, (child) => (mentions(child) ? true : undefined)) ?? false); + return ( + argNode.parameters.nodes.some((other) => other !== param && mentions(other)) || + mentions(argNode.body) + ); +} + /** Preserve the checker-selected payload ABI when a callback's runtime * return gained only undefined. The array state byte carries that value. */ export function callbackArrayElement( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8014-foreign-element-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8014-foreign-element-callbacks.ts.json new file mode 100644 index 0000000000..7754e8cb8f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8014-foreign-element-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8014-foreign-element-callbacks.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8014-foreign-element-callbacks.ts b/tests/corpus/8014-foreign-element-callbacks.ts new file mode 100644 index 0000000000..b971b40516 --- /dev/null +++ b/tests/corpus/8014-foreign-element-callbacks.ts @@ -0,0 +1,60 @@ +// Callbacks over a number[] or string[] that holds elements written +// through an unknown[] view, when the callback never reads the element: +// it declares no element parameter, or never references the one it +// declares. The methods visit every element as in Node, and filter keeps +// a written element as it is. + +const numbers: number[] = [1, 2]; +const numbersView: unknown[] = numbers; +numbersView.push("x", undefined, { k: 1 }); +numbers.push(5); + +// Inline callbacks (fused loops). +console.log(numbers.filter(() => true)); +console.log(numbers.filter((_, i) => i % 2 === 0)); +console.log(numbers.map(() => 0)); +console.log(numbers.map((_, i) => i * 10)); +console.log(numbers.some(() => false), numbers.every((_v) => true)); +console.log(numbers.findIndex((_, i) => i === 2), numbers.findLastIndex((_, i) => i < 3)); +console.log(numbers.find(() => false), numbers.findLast((_, i) => i === 0)); +console.log(numbers.reduce((s) => s + 1, 0), numbers.reduce((s, v) => s + 1, 100)); +console.log(numbers.reduceRight((s, _v, i) => s + i, 0)); +let visits = 0; +numbers.forEach((_, i) => { + visits += i; +}); +console.log(visits); +console.log(numbers.filter((_, i) => i > 1).map((_, i) => i)); +console.log(numbers.flatMap(() => [7]), numbers.flatMap((_, i) => [i, -i])); +console.log(numbers.filter(() => true).length, typeof (numbers.filter(() => true) as unknown[])[2]); + +// Callback values (shared helpers). +const always = (): boolean => true; +const never = (): boolean => false; +const count = (s: number): number => s + 1; +console.log(numbers.filter(always)); +console.log(numbers.some(never), numbers.every(always), numbers.find(never)); +console.log(numbers.findIndex(never), numbers.reduce(count, 0)); +console.log(numbers.map(always)); + +// A kept written element is the same value in the result. +const kept = numbers.filter(always) as unknown[]; +console.log(kept[2] === "x", kept[3] === undefined, 3 in kept, kept[4], kept.length); + +// string[] the same way. +const words: string[] = ["a", "b"]; +const wordsView: unknown[] = words; +wordsView.push(7, null); +console.log(words.filter(() => true), words.map((_, i) => `#${i}`)); +console.log(words.filter((_w, i) => i !== 1), words.reduce((s) => s + 1, 0)); + +// Holes stay holes and are skipped as in Node. +const sparse: number[] = [1]; +const sparseView: unknown[] = sparse; +sparseView[3] = "z"; +console.log(sparse.filter(() => true), sparse.map((_, i) => i), sparse.findIndex((_, i) => i === 1)); + +// A parameter used only through a shorthand property is still read. +const plain = [1, 2, 3]; +console.log(plain.map((v) => ({ v })), plain.reduce((acc, v) => ({ sum: acc.sum + v, v }), { sum: 0, v: 0 })); +console.log(plain.filter((v) => [{ v }].length > 0 && v > 1)); diff --git a/tests/harness/corpus-costs/8014-foreign-element-callbacks.ts.json b/tests/harness/corpus-costs/8014-foreign-element-callbacks.ts.json new file mode 100644 index 0000000000..981bcbb55b --- /dev/null +++ b/tests/harness/corpus-costs/8014-foreign-element-callbacks.ts.json @@ -0,0 +1 @@ +{ "plain": 5089, "san": 8118 } From 744585944aedf0eabcc25d801c4785724300f1aa Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 17:30:55 -0700 Subject: [PATCH 07/49] Keep the localized-failure fixture's field failing after interface any parameters map Co-Authored-By: Claude Opus 5.5 --- tests/coverage-fixtures/localized-failure.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/coverage-fixtures/localized-failure.ts b/tests/coverage-fixtures/localized-failure.ts index ed4d30e8c7..84ae343b46 100644 --- a/tests/coverage-fixtures/localized-failure.ts +++ b/tests/coverage-fixtures/localized-failure.ts @@ -3,7 +3,7 @@ // each one rejects today. interface Tool { name: string; - execute(id: string, params: any): Promise; + hooks: Map string> | AbortSignal | (() => void) | Set; } type Progress = (done: number, total: number) => void; type Callbacks = Progress | AbortSignal | Map string> | Set; From ac31a15a62ec33bb9514532e41410cbac3fcfe3e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:33:16 -0700 Subject: [PATCH 08/49] Hold locals and module globals whose declared type does not compile as checked dynamic slots Stage L1 of the localized-failure design. A local, loop variable or module global whose declared TypeScript type has no static lowering used to reject its declaration statement, and every later use of the binding failed with it. Where that failure is about to enter the build (the statement's poison in a TypeScript file, or the module global's registration), the failing type now joins the lowerer's degraded types and the unit lowers again: the type maps to the checked dynamic wherever its strict mapping fails, so the slot holds values by reference (typed capsules, live records and arrays, function thunks) and typed reads out of it are checked. Each such slot is an SC6104 performance warning, listed under coverage's statement counts and in the JSON envelope; coverage --fail-on=dynamic-slots fails on them, and --tier=static-only keeps today's refusals. A program that compiles today reaches neither commit point, so nothing degrades and its output is unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/cli/usage.ts | 2 +- packages/compiler/src/coverage/report.ts | 53 ++++++- .../compiler/src/coverage/verdict-cache.ts | 6 +- .../compiler/src/diagnostics/dynamic-slots.ts | 49 ++++++ packages/compiler/src/diagnostics/envelope.ts | 9 ++ packages/compiler/src/frontend/analysis.ts | 1 + .../src/frontend/lowering/lower-modules.ts | 31 +++- .../src/frontend/lowering/lower-stmts.ts | 140 +++++++++++------- .../compiler/src/frontend/lowering/lowerer.ts | 112 +++++++++++++- packages/compiler/src/frontend/type-mapper.ts | 15 ++ tests/corpus/localized-failure-held-values.ts | 70 +++++++++ tests/corpus/localized-failure-locals.ts | 43 ++++++ .../coverage-explain-localized-failure.txt | 14 +- .../localized-failure-held-values.ts.json | 1 + .../localized-failure-locals.ts.json | 1 + tests/harness/coverage.test.ts | 43 +++++- 16 files changed, 517 insertions(+), 73 deletions(-) create mode 100644 packages/compiler/src/diagnostics/dynamic-slots.ts create mode 100644 tests/corpus/localized-failure-held-values.ts create mode 100644 tests/corpus/localized-failure-locals.ts create mode 100644 tests/harness/corpus-costs/localized-failure-held-values.ts.json create mode 100644 tests/harness/corpus-costs/localized-failure-locals.ts.json diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index f8b6a48692..7d27248107 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -31,7 +31,7 @@ Options: (native-link-info implies --emit=obj and never links; diagnostics prints the versioned JSON diagnostics envelope for build and coverage) - --fail-on + --fail-on coverage only: exit 1 when the entry path has blockers (a build would fail), or also when it has Node divergences, likely-hot functions that use untyped diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index dabbc96176..d78120f89d 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -22,6 +22,11 @@ import { type TiersEnvelope, } from "../diagnostics/envelope.js"; import { renderDiagnostics } from "../diagnostics/render.js"; +import { + dynamicSlotLabel, + isDynamicSlotCode, + type DynamicSlotEntry, +} from "../diagnostics/dynamic-slots.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { ProvenanceSources } from "../frontend/provenance-registry.js"; import type { @@ -59,6 +64,9 @@ export interface CoverageInput { * leaves untyped. Never blockers; `--fail-on=dynamic` opts into failing * on it. */ performance?: ScrDiagnostic[]; + /** Localized failure: the checked dynamic slots (SC6104, also in + * `performance`). `--fail-on=dynamic-slots` fails on them. */ + dynamicSlots?: DynamicSlotEntry[]; /** Each dependency's native compilation outcome, with the first refusal * reason for a refused package. */ npmStatic?: NpmStaticStatus[]; @@ -238,6 +246,10 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions ` ${c(DIM, `(${skipped} function${skipped === 1 ? "" : "s"} not analyzed — signature blocked; their statements count as not static)`)}`, ); } + renderDynamicSlots(input.dynamicSlots ?? [], out, c, (loc) => { + const p = locator.position(loc.file, loc.start); + return `${displayPath(loc.file, opts.root)}:${p.line}:${p.column}`; + }); out.push(""); // Report native package compilation and the first reason for each refusal. @@ -395,7 +407,7 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions renderRows(groupBlockers(divergences), YELLOW); }; // The performance warning: its message names the functions. - const performance = input.performance ?? []; + const performance = (input.performance ?? []).filter((d) => !isDynamicSlotCode(d.code)); const renderPerformance = () => { for (const d of performance) { out.push(""); @@ -529,6 +541,7 @@ export type CoverageFailOn = | "blockers" | "divergences" | "dynamic" + | "dynamic-slots" | "node-islands" | "eager-node-islands" | "island-gaps"; @@ -537,6 +550,7 @@ export const COVERAGE_FAIL_ON: readonly CoverageFailOn[] = [ "blockers", "divergences", "dynamic", + "dynamic-slots", "node-islands", "eager-node-islands", "island-gaps", @@ -552,7 +566,9 @@ export function coveragePasses(input: CoverageInput, failOn?: CoverageFailOn): b // runtime fences build (the statement throws only if it executes). if (input.diagnostics.length > 0) return false; if (failOn === "divergences" && (input.divergences?.length ?? 0) > 0) return false; - if (failOn === "dynamic" && (input.performance?.length ?? 0) > 0) return false; + if (failOn === "dynamic" && (input.performance ?? []).some((d) => !isDynamicSlotCode(d.code))) + return false; + if (failOn === "dynamic-slots" && (input.dynamicSlots?.length ?? 0) > 0) return false; // The Node island gates (report-only islands count: they are what a // build would choose). Every island evaluates eagerly until deferred // evaluation lands, so eager-node-islands equals node-islands for now. @@ -624,6 +640,19 @@ export function coverageEnvelope( percentStatic: total === 0 ? 100 : Math.floor((ok / total) * 100), }, }), + ...(input.dynamicSlots !== undefined + ? { + dynamicSlots: input.dynamicSlots.map((slot) => ({ + file: slot.loc.file, + ...locator.position(slot.loc.file, slot.loc.start), + name: slot.name, + kind: slot.slot, + declaredType: slot.declaredType, + code: slot.code, + reason: slot.reason, + })), + } + : {}), ...(input.tiers !== undefined ? { tiers: tiersEnvelope(input.tiers, locator) } : {}), ...(input.wasmModules !== undefined ? { wasmModules: input.wasmModules } : {}), ...(input.nodeIslands !== undefined @@ -719,6 +748,26 @@ function unitMatches(u: DemotionUnit, query: string): boolean { ); } +const DYNAMIC_SLOTS_SHOWN = 20; + +/** The dynamic slots under the statement counts: each slot's declared + * type, and why it has no static lowering. */ +function renderDynamicSlots( + slots: readonly DynamicSlotEntry[], + out: string[], + c: (code: string, s: string) => string, + where: (loc: SrcLoc) => string, +): void { + if (slots.length === 0) return; + out.push( + ` dynamic slots ${slots.length} ${c(DIM, "(declared types with no static lowering, held as checked-dynamic values; SC6104)")}`, + ); + for (const slot of slots.slice(0, DYNAMIC_SLOTS_SHOWN)) + out.push(` ${dynamicSlotLabel(slot)} ${c(DIM, where(slot.loc))}`); + if (slots.length > DYNAMIC_SLOTS_SHOWN) + out.push(` ${c(DIM, `and ${slots.length - DYNAMIC_SLOTS_SHOWN} more`)}`); +} + const SLOTS_SHOWN = 40; /** The localized-failure section of --explain-dynamic (report-only): the diff --git a/packages/compiler/src/coverage/verdict-cache.ts b/packages/compiler/src/coverage/verdict-cache.ts index c92cdc45e3..dfaaa5c98c 100644 --- a/packages/compiler/src/coverage/verdict-cache.ts +++ b/packages/compiler/src/coverage/verdict-cache.ts @@ -7,6 +7,7 @@ * still hold. With a semantic matcher (the Node frontend's), edits that * only touch TypeScript comments also reuse it, with every reported * location moved to the edited text. */ +import type { DynamicSlotEntry } from "../diagnostics/dynamic-slots.js"; import { createHash } from "node:crypto"; import { mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; @@ -23,7 +24,7 @@ import { type FrontendInputSnapshot, } from "../frontend/input-tracker.js"; -const SCHEMA = "scriptc.coverage-verdict.v3"; +const SCHEMA = "scriptc.coverage-verdict.v4"; /** Key-value storage for verdict entries (best effort: failures miss). */ export interface VerdictStore { @@ -41,6 +42,7 @@ export interface CachedCoverage { unreached?: { stats: LowerStats; diagnostics: ScrDiagnostic[]; runtimeFences?: ScrDiagnostic[] }; divergences?: ScrDiagnostic[]; performance?: ScrDiagnostic[]; + dynamicSlots?: DynamicSlotEntry[]; npmStatic?: NpmStaticStatus[]; wasmModules?: WasmModuleReport[]; nodeIslands?: NodeIslandReport; @@ -158,6 +160,7 @@ function cacheable(coverage: AnalyzeResult["coverage"]): CachedCoverage | null { if (coverage.unreached !== undefined) out.unreached = coverage.unreached; if (coverage.divergences !== undefined) out.divergences = coverage.divergences; if (coverage.performance !== undefined) out.performance = coverage.performance; + if (coverage.dynamicSlots !== undefined) out.dynamicSlots = coverage.dynamicSlots; if (coverage.npmStatic !== undefined) out.npmStatic = coverage.npmStatic; if (coverage.wasmModules !== undefined) out.wasmModules = coverage.wasmModules; if (coverage.nodeIslands !== undefined) out.nodeIslands = coverage.nodeIslands; @@ -177,6 +180,7 @@ function restore(cached: CachedCoverage, sources: CachedSource[]): AnalyzeResult if (cached.unreached !== undefined) coverage.unreached = cached.unreached; if (cached.divergences !== undefined) coverage.divergences = cached.divergences; if (cached.performance !== undefined) coverage.performance = cached.performance; + if (cached.dynamicSlots !== undefined) coverage.dynamicSlots = cached.dynamicSlots; if (cached.npmStatic !== undefined) coverage.npmStatic = cached.npmStatic; if (cached.wasmModules !== undefined) coverage.wasmModules = cached.wasmModules; if (cached.nodeIslands !== undefined) coverage.nodeIslands = cached.nodeIslands; diff --git a/packages/compiler/src/diagnostics/dynamic-slots.ts b/packages/compiler/src/diagnostics/dynamic-slots.ts new file mode 100644 index 0000000000..09b4cd6706 --- /dev/null +++ b/packages/compiler/src/diagnostics/dynamic-slots.ts @@ -0,0 +1,49 @@ +/* SC6104, the dynamic-slot performance warning (localized-failure design + * §3.9): a slot whose declared TypeScript type has no static lowering holds + * a checked-dynamic value instead of rejecting the declaration around it. + * Its wording lives here; the slots come from the lowering's commit points + * (Lowerer.degradeAtCommit). Imports nothing heavy, like dynamic-tier.ts. */ +import type { SrcLoc } from "../ir/ir.js"; +import type { ScrDiagnostic, TypeFailureSlot } from "./diagnostic.js"; + +export const DYNAMIC_SLOT_CODE = "SC6104"; + +/** One dynamic slot, as coverage reports it. */ +export interface DynamicSlotEntry { + loc: SrcLoc; + name: string; + slot: TypeFailureSlot; + declaredType: string; + /** The diagnostic the slot's declaration would have failed with. */ + code: string; + reason: string; +} + +const SLOT_WORD: Record = { + field: "field", + param: "parameter", + return: "return value of", + local: "local", + global: "module global", + loop: "loop variable", + member: "member", + expression: "expression", +}; + +/** `'_customTools' (declared 'ToolDefinition[]')`, with the slot kind. */ +export function dynamicSlotLabel(entry: DynamicSlotEntry): string { + return `${SLOT_WORD[entry.slot]} '${entry.name}' (declared '${entry.declaredType}')`; +} + +export function dynamicSlotDiag(entry: DynamicSlotEntry): ScrDiagnostic { + return { + code: DYNAMIC_SLOT_CODE, + message: `${dynamicSlotLabel(entry)} is held as a checked-dynamic value: its type has no static lowering (${entry.reason}); reads into typed code are checked`, + loc: entry.loc, + hint: "restate the declared type inside the compilable set to keep this slot static; scriptc coverage lists dynamic slots, and --fail-on=dynamic-slots fails on them", + }; +} + +export function isDynamicSlotCode(code: string): boolean { + return code === DYNAMIC_SLOT_CODE; +} diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index e64f35824d..f6f8b9d3da 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -164,6 +164,15 @@ export interface DiagnosticsEnvelope { }; /** build only: the produced artifact path. */ artifact?: string; + /** coverage only, when there are any: the checked dynamic slots + * (localized failure, SC6104). */ + dynamicSlots?: (SourcePosition & { + name: string; + kind: string; + declaredType: string; + code: string; + reason: string; + })[]; /** coverage --explain-dynamic only: the report-only tier assignment. */ tiers?: TiersEnvelope; /** coverage only: the WebAssembly modules the build can see, with their diff --git a/packages/compiler/src/frontend/analysis.ts b/packages/compiler/src/frontend/analysis.ts index 848306b66b..d2e8814737 100644 --- a/packages/compiler/src/frontend/analysis.ts +++ b/packages/compiler/src/frontend/analysis.ts @@ -132,6 +132,7 @@ export function analyzeWithFrontend( ...(lowered.runtimeFences.length > 0 ? { runtimeFences: lowered.runtimeFences } : {}), ...(lowered.divergences !== undefined ? { divergences: lowered.divergences } : {}), ...(lowered.performance !== undefined ? { performance: lowered.performance } : {}), + ...(lowered.dynamicSlots !== undefined ? { dynamicSlots: lowered.dynamicSlots } : {}), ...(lowered.unreached ? { unreached: lowered.unreached } : {}), ...(fe.npmStatic.length > 0 ? { npmStatic: fe.npmStatic } : {}), // --provenance-sources: the per-package attribution inputs (the diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 3f4a70c744..dd0467bd0f 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -1,3 +1,4 @@ +import type { TypeFailureSlot } from "../../diagnostics/diagnostic.js"; import { THREADS_MODULE } from "../threads-import.js"; import { positionTag } from "./structural-positions.js"; import { EXPERIMENTAL_MODULE } from "../experimental-import.js"; @@ -2048,7 +2049,15 @@ export function collectGlobals( // Destructuring declarations register EVERY bound identifier (the // desugar in the init function assigns the pre-registered globals, // exactly like plain declarations). - for (const nameNode of boundIdentifiersOf(decl.name)) { + const names = boundIdentifiersOf(decl.name); + // Localized failure: a degradable type failure at a module global + // degrades that type and the same name registers again + // (globalSlotRetry), at most MAX_GLOBAL_RETRIES times. + let retries = 0; + let retriedIndex = -1; + for (let nameIndex = 0; nameIndex < names.length; nameIndex++) { + const nameNode = names[nameIndex]!; + if (nameIndex !== retriedIndex) retries = 0; const diagsBefore = lowerer.diags.length; try { // Checked JavaScript factories can expose inferred callables by @@ -2700,6 +2709,12 @@ export function collectGlobals( } } catch (e) { if (!(e instanceof PoisonError)) throw e; + if (retries < MAX_GLOBAL_RETRIES && globalSlotRetry(lowerer, sf, e)) { + retries++; + retriedIndex = nameIndex; + nameIndex--; + continue; + } // diagnostic already recorded; lowering the statement poisons // too — and in a JS file BOTH defer to the statement's // runtime fence (deferJsCollectionDiags). @@ -2710,6 +2725,20 @@ export function collectGlobals( } } +/** Localized failure's commit point for module globals (design §3.3): + * collectGlobals commits a global's type failure directly, before any body + * lowers, so it degrades here and the registration runs again. */ +function globalSlotRetry(lowerer: Lowerer, sf: ts.SourceFile, e: PoisonError): boolean { + if (isJsSourceFile(sf)) return false; + const slot = lowerer.degradeAtCommit(e, GLOBAL_SLOTS); + if (slot === null) return false; + lowerer.noteDynamicSlot(slot); + return true; +} + +const MAX_GLOBAL_RETRIES = 16; +const GLOBAL_SLOTS: ReadonlySet = new Set(["global"]); + /** Registers the entry-init note for a `var` module global: JS hoists * module vars to `undefined` at module entry, so an undefined-armed slot * must hold the interned undefined arm BEFORE the body runs (a function diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 4cf3a342cb..7d5273e074 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -40,8 +40,8 @@ import { isJsonRequireDecl } from "./json-require.js"; import { isTimeValueDateDeclaration } from "./date-values.js"; import { bindingSource } from "../binding-source.js"; import { lowerConstructorReturn } from "./constructor-returns.js"; -import type { Lowerer } from "./lowerer.js"; -import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import type { DynamicSlot, Lowerer } from "./lowerer.js"; +import type { ScrDiagnostic, TypeFailureSlot } from "../../diagnostics/diagnostic.js"; import { statementGroupKey } from "./type-failure.js"; import { cjsExportValueByName, @@ -754,57 +754,28 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir const sinkMark = lowerer.diagSinkMark(); const touched = new Set(); lowerer.failureTouches.push(touched); + const pendingSlots: DynamicSlot[] = []; try { - let lowered: IrStmt | IrStmt[] | null; - let appended = false; - if ( - ts.isVariableStatement(stmt) && - (stmt.declarationList.flags & ts.NodeFlags.Using) !== 0 - ) { - const registrations = lowerUsingStatement(lowerer, stmt); - const accounting: IrStmt[] = []; - for (const registration of registrations) { - out.push(...registration.acquire); - accounting.push(...registration.acquire, ...registration.cleanup); - if (registration.cleanup.length > 0) { - usingMarkers.push({ - start: out.length, - cleanup: registration.cleanup, - loc: registration.loc, - }); - } + for (let attempt = 0; ; attempt++) { + try { + lowerOneStatement(lowerer, stmt, out, usingMarkers, diagsBefore, deferFences); + break; + } catch (e) { + // Localized failure's statement commit point (design §3.3): a + // degradable type failure at a local, loop variable or module + // global degrades that type and the statement lowers again. + const slot = + attempt < MAX_DEGRADE_RETRIES && !deferFences + ? lowerer.degradeAtCommit(e, STATEMENT_SLOTS) + : null; + if (slot === null) throw e; + // A failed attempt adds nothing to `out` but the TDZ boxes + // forward captures declared (they stay declared), so the + // retry appends after them. + pendingSlots.push(slot); } - lowered = accounting; - appended = true; - } else { - lowered = lowerer.lowerStmt(stmt); - } - // The lib-boundary chokepoint (lib-boundary.ts): checked-dynamic - // arguments that reached builtin-call slots without the checked - // coercion get their dynCheck here, and the uncoercible ones - // fence — inside this statement's poison window, so a JS fence - // becomes the statement's runtimeFence exactly like every other - // deferred rejection. - if (lowered) { - const body: IrStmt[] = []; - if (Array.isArray(lowered)) body.push(...lowered); - else body.push(lowered); - const rewritten = lowerSurplusCalls(lowerer, body); - enforceLibBoundary(lowerer, rewritten); - if (!appended) out.push(...rewritten); - } - // A JS statement that lowered CLEAN can still have flushed - // DEFERRED collection diagnostics (a symbol lookup on an alias - // resolves through flushDeferred — informational here, not a - // blocker of THIS statement): those belong to the runtime-fence - // ledger, not the build — real uses of the broken declaration - // meet their own per-site fences. - if (deferFences && lowerer.diagSink === null && lowerer.diags.length > diagsBefore) { - const flushed = lowerer.diags.splice(diagsBefore); - const ice = flushed.filter((d) => d.code === "SC9001"); - lowerer.diags.push(...ice); - lowerer.runtimeFences.push(...flushed.filter((d) => d.code !== "SC9001")); } + for (const slot of pendingSlots) lowerer.noteDynamicSlot(slot); } catch (e) { // A Go panic inside tsgo, surfaced by the sync channel as a // thrown Error (server intact — the prefetch fence's finding): @@ -878,6 +849,75 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir return out; } +/** Localized failure: retries a degradable statement-level type failure + * at most this many times (each retry degrades one more type). */ +const MAX_DEGRADE_RETRIES = 16; + +/** The slot kinds lowerStmts' commit point degrades (stage L1). */ +const STATEMENT_SLOTS: ReadonlySet = new Set([ + "local", + "global", + "loop", +]); + +/** One attempt at lowering a statement of lowerStmts into `out`. */ +function lowerOneStatement( + lowerer: Lowerer, + stmt: ts.Statement, + out: IrStmt[], + usingMarkers: UsingMarker[], + diagsBefore: number, + deferFences: boolean, +): void { + let lowered: IrStmt | IrStmt[] | null; + let appended = false; + if (ts.isVariableStatement(stmt) && (stmt.declarationList.flags & ts.NodeFlags.Using) !== 0) { + const registrations = lowerUsingStatement(lowerer, stmt); + const accounting: IrStmt[] = []; + for (const registration of registrations) { + out.push(...registration.acquire); + accounting.push(...registration.acquire, ...registration.cleanup); + if (registration.cleanup.length > 0) { + usingMarkers.push({ + start: out.length, + cleanup: registration.cleanup, + loc: registration.loc, + }); + } + } + lowered = accounting; + appended = true; + } else { + lowered = lowerer.lowerStmt(stmt); + } + // The lib-boundary chokepoint (lib-boundary.ts): checked-dynamic + // arguments that reached builtin-call slots without the checked + // coercion get their dynCheck here, and the uncoercible ones + // fence — inside this statement's poison window, so a JS fence + // becomes the statement's runtimeFence exactly like every other + // deferred rejection. + if (lowered) { + const body: IrStmt[] = []; + if (Array.isArray(lowered)) body.push(...lowered); + else body.push(lowered); + const rewritten = lowerSurplusCalls(lowerer, body); + enforceLibBoundary(lowerer, rewritten); + if (!appended) out.push(...rewritten); + } + // A JS statement that lowered CLEAN can still have flushed + // DEFERRED collection diagnostics (a symbol lookup on an alias + // resolves through flushDeferred — informational here, not a + // blocker of THIS statement): those belong to the runtime-fence + // ledger, not the build — real uses of the broken declaration + // meet their own per-site fences. + if (deferFences && lowerer.diagSink === null && lowerer.diags.length > diagsBefore) { + const flushed = lowerer.diags.splice(diagsBefore); + const ice = flushed.filter((d) => d.code === "SC9001"); + lowerer.diags.push(...ice); + lowerer.runtimeFences.push(...flushed.filter((d) => d.code !== "SC9001")); + } +} + /** Whether a statement list belongs to a function body (at any block * depth) rather than to a module's top level. */ function enclosedByFunction(stmt: ts.Statement): boolean { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 65b66c298a..6b1174d68e 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -208,8 +208,10 @@ import type { ScrDiagnostic, TypeFailure, TypeFailureFamily, + TypeFailureSlot, } from "../../diagnostics/diagnostic.js"; import { declarationGroupKey, typeFailureOf } from "./type-failure.js"; +import { dynamicSlotDiag, type DynamicSlotEntry } from "../../diagnostics/dynamic-slots.js"; import { unsupportedAnyOperationDiag, blockedBindingUseDiag, @@ -718,14 +720,27 @@ interface GenericDemandOwner { classDemands: ClassInfo[]; } +/** A slot the localized-failure commit points turned into a checked + * dynamic slot (SC6104): where it is, what its declared type is, and the + * diagnostic it would have failed with. */ +export interface DynamicSlot { + loc: SrcLoc; + failure: TypeFailure; + code: string; + reason: string; +} + export class PoisonError extends Error { /** Set when a type with no static lowering poisoned a slot (badType and * the dyn class field policy): what failed, for the localized-failure * commit points. Report-only in stage L0. */ readonly failure: TypeFailure | undefined; - constructor(failure?: TypeFailure) { + /** The checker type that failed (with `failure`). */ + readonly failedType: ts.Type | undefined; + constructor(failure?: TypeFailure, failedType?: ts.Type) { super(); this.failure = failure; + this.failedType = failedType; } } @@ -842,6 +857,9 @@ export interface LowerResult { /** Performance warnings (SC61xx): the likely-hot functions the source * leaves untyped (lowering/dynamic-tier.ts). Never failures. */ performance?: ScrDiagnostic[]; + /** Localized failure: the checked dynamic slots the commit points + * created (each also an SC6104 in `performance`), present when any. */ + dynamicSlots?: DynamicSlotEntry[]; /** Functions lowering placed in the dynamic tier (ir/tiers.ts), present * when there are any. */ dynamicFunctions?: DynamicFunction[]; @@ -1257,6 +1275,22 @@ export function lowerToIr( timing("dynamic-tier-scan", { found: hotDynamic === null ? 0 : 1 }); if (hotDynamic !== null) result = { ...result, performance: [hotDynamic] }; } + // Localized failure (SC6104): one performance warning per dynamic slot. + if (resultLowerer.dynamicSlots.length > 0) { + const dynamicSlots: DynamicSlotEntry[] = resultLowerer.dynamicSlots.map((slot) => ({ + loc: slot.loc, + name: slot.failure.name, + slot: slot.failure.slot, + declaredType: slot.failure.declaredType, + code: slot.code, + reason: slot.reason, + })); + result = { + ...result, + performance: [...(result.performance ?? []), ...dynamicSlots.map(dynamicSlotDiag)], + dynamicSlots, + }; + } // Contract types (@scriptc/types): written annotations, checked against // the IR by the compile pipeline (contracts/check.ts). // Acyclic object types name the IR unit the program lowered them to; a @@ -3720,6 +3754,13 @@ export class Lowerer { * its lowering touched. A statement that then poisons attributes its * diagnostics to them (ScrDiagnostic.cause). */ readonly failureTouches: Set[] = []; + /** Localized failure: checker types (by id) whose strict mapping failed + * at a committed slot, with their lenient mapping. Filled only at the + * commit points (degradeAtCommit), so a program that compiles today + * never fills it. */ + readonly degradedTypes = new Map(); + /** The dynamic slots the commit points created: one SC6104 each. */ + readonly dynamicSlots: DynamicSlot[] = []; /** True while collectProgram runs: resolution helpers must not flush * deferred diagnostics (collection itself resolves symbols — extends * clauses — and collection order must not decide what reports). */ @@ -3837,6 +3878,10 @@ export class Lowerer { resolveTypeParam: this.typeParamResolver, resolveTypeParamTs: this.typeParamTsResolver, resolveBoundAliasTs: this.boundAliasTsResolver, + degradedType: (type) => { + const id = (type as { id?: number }).id; + return id === undefined ? null : (this.degradedTypes.get(id)?.lenient ?? null); + }, isErasing: () => this.erasingDepth > 0, genericClassInstance: (decl, ref, mapArgument) => this.genericClassInstanceType(decl, ref, mapArgument), @@ -9270,7 +9315,65 @@ export class Lowerer { const failure = typeFailureOf(node, this.checker.typeToString(type), family); diag.typeFailure = failure; this.pushDiag(diag); - throw new PoisonError(failure); + throw new PoisonError(failure, type); + } + + /** Localized failure's commit point (design §3.3): `e` poisoned a unit + * whose failure is about to enter the build. When it is a degradable type + * failure at a slot kind in `slots`, the failing type joins + * degradedTypes (its lenient mapping is the checked dynamic, whole slot), + * the failure's own diagnostic leaves the build, and the caller lowers + * the unit again. Returns the slot to record once that retry succeeds, + * or null to commit as today. Never inside a capture buffer (a probe or a + * deferred collection) and never under --tier=static-only. */ + degradeAtCommit(e: unknown, slots: ReadonlySet): DynamicSlot | null { + if (this.tierMode !== "auto" || this.diagSink !== null) return null; + if (!(e instanceof PoisonError)) return null; + const failure = e.failure; + const type = e.failedType; + if (failure === undefined || type === undefined) return null; + if (!failure.degradable || failure.family === "dyn-field-policy") return null; + if (!slots.has(failure.slot)) return null; + const id = (type as { id?: number }).id; + // Already degraded and still failing: no progress, commit as today. + if (id === undefined || this.degradedTypes.has(id)) return null; + let committed: ScrDiagnostic | undefined; + for (let i = this.diags.length - 1; i >= 0; i--) { + if (this.diags[i]!.typeFailure !== failure) continue; + committed = this.diags[i]; + this.diags.splice(i, 1); + } + if (committed === undefined) return null; + this.degradedTypes.set(id, { lenient: DYN, code: committed.code, reason: committed.message }); + return { loc: committed.loc, failure, code: committed.code, reason: committed.message }; + } + + /** A declaration slot whose type maps through degradedTypes is a + * dynamic slot too, though only the first such slot failed: record it + * (outside probes; the lookup is skipped while nothing is degraded). */ + noteDegradedSlot(node: ts.Node, type: ts.Type): void { + if (this.diagSink !== null) return; + const degraded = this.degradedTypes.get((type as { id?: number }).id ?? -1); + if (degraded === undefined) return; + const failure = typeFailureOf(node, this.checker.typeToString(type), "unsupported"); + if (failure.slot === "expression" || failure.slot === "member") return; + this.noteDynamicSlot({ + loc: locOf(node), + failure, + code: degraded.code, + reason: degraded.reason, + }); + } + + /** Records a dynamic slot whose unit lowered with it (one per site). */ + noteDynamicSlot(slot: DynamicSlot): void { + const seen = this.dynamicSlots.some( + (s) => + s.loc.file === slot.loc.file && + s.loc.start === slot.loc.start && + s.loc.end === slot.loc.end, + ); + if (!seen) this.dynamicSlots.push(slot); } /** The SC1090 refusal of checked-dynamic class fields in TypeScript @@ -9285,7 +9388,7 @@ export class Lowerer { ); diag.typeFailure = failure; this.pushDiag(diag); - throw new PoisonError(failure); + throw new PoisonError(failure, this.typeOf(node)); } badType(node: ts.Node, type: ts.Type): never { @@ -9969,6 +10072,7 @@ export class Lowerer { // f64 — but must not: pre-empt the mapping so the JS fallback below // answers instead. const mapped = neverTaintedJsType(this, node, t) ? null : this.mapTypeOf(t); + if (mapped !== null && this.degradedTypes.size > 0) this.noteDegradedSlot(node, t); if (!mapped) { const argumentsType = jsArgumentsFunctionType(this, t); if (argumentsType !== null) return argumentsType; @@ -12686,6 +12790,8 @@ export class Lowerer { /* ── scoping and captures ─────────────────────────────────────────── */ declareLocal(nameNode: ts.Node, name: string, type: IrType, mutable: boolean): IrLocal { + if (type.kind === "dyn" && this.degradedTypes.size > 0) + this.noteDegradedSlot(nameNode, this.typeOf(nameNode)); const symbol = this.checker.getSymbolAtLocation(nameNode); const local = declareContextLocal( this.ctx, diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 4b1bf2a42b..d4d0ab42ca 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1282,6 +1282,11 @@ export interface TypeMapperCtx { * checker type its signature instantiates, or null. Consulted only when * the symbolic form does not map. */ resolveBoundAliasTs?: TypeParamTsResolver; + /** Localized failure: the lenient mapping of a type whose strict mapping + * failed at a committed slot (Lowerer.degradedTypes), or null. Consulted + * only after the strict mapping (and its bound-alias retry) returns null, + * at every depth, so every occurrence of a degraded type maps alike. */ + degradedType?: (type: ts.Type) => IrType | null; /** GENERIC program classes (monomorphization by flow): the mapping of a * concrete instantiation reference (`Box`) — the Lowerer * registers/reuses the instantiation (`Box%0`) and answers its object @@ -1856,6 +1861,16 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (mapped !== null) contextResolutions++; } } + // Localized failure: a type that failed at a committed slot maps to its + // lenient form. It counts as a context resolution so neither it nor a + // type containing it is memoized: the strict form may map elsewhere. + if (mapped === null && ctx.degradedType) { + const lenient = ctx.degradedType(type); + if (lenient !== null) { + mapped = lenient; + contextResolutions++; + } + } if ( mapped !== null && memoKey !== null && diff --git a/tests/corpus/localized-failure-held-values.ts b/tests/corpus/localized-failure-held-values.ts new file mode 100644 index 0000000000..ee52498c1a --- /dev/null +++ b/tests/corpus/localized-failure-held-values.ts @@ -0,0 +1,70 @@ +// Localized failure, stage L1: values of every kind held in a local or +// module global whose declared type has no static lowering keep their +// identity and behavior. +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + norm(): number { + return Math.abs(this.x) + Math.abs(this.y); + } +} +interface Cfg { + name: string; + depth: number; +} +type Anything = + | Point + | Cfg + | number[] + | Map string> + | AbortSignal + | (() => number) + | Promise; + +const p = new Point(3, -4); +const cfg: Cfg = { name: "a", depth: 1 }; +const xs = [1, 2, 3]; +let counter = 0; +const tick = (): number => ++counter; + +// A module global holding a class instance. +let held: Anything = p; +if (held instanceof Point) { + console.log("point", held.norm(), held === p); + held.x = 10; +} +console.log("typed alias", p.x, p.norm()); + +// A record: mutation through the hole is visible in the typed alias. +held = cfg; +if (!(held instanceof Point) && !Array.isArray(held) && typeof held === "object" && "depth" in held) { + held.depth = 5; + console.log("record", held.name, held === cfg); +} +console.log("typed alias", cfg.depth, JSON.stringify(cfg), Object.keys(cfg).join(",")); + +// An array: pushes through the hole show in typed code. +held = xs; +if (Array.isArray(held)) { + held.push(4); + console.log("array", held.length, held === xs); +} +console.log("typed alias", xs.length, xs.join("+")); + +function main(): void { + // A local holding a closure, called through the hole. + let local: Anything = tick; + if (typeof local === "function") { + console.log("closure", local(), local(), counter, local === tick); + } + // A promise, awaited through the hole. + local = Promise.resolve(42); + if (local instanceof Promise) { + local.then((v) => console.log("promise", v)); + } +} +main(); diff --git a/tests/corpus/localized-failure-locals.ts b/tests/corpus/localized-failure-locals.ts new file mode 100644 index 0000000000..418b86d877 --- /dev/null +++ b/tests/corpus/localized-failure-locals.ts @@ -0,0 +1,43 @@ +// Localized failure, stage L1: locals, loop variables and module globals +// whose declared type has no static lowering hold checked-dynamic values. +type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; + +const report: Progress = (done, total) => console.log(`progress ${done}/${total}`); +const seen = new Set([1, 2, 3]); + +// A module global, written with typed values of several arms. +let current: Callbacks | undefined = report; + +function describe(label: string): void { + // A local of the same declared type. + const local: Callbacks | undefined = current; + if (local === undefined) { + console.log(label, "none"); + } else if (typeof local === "function") { + local(1, 2); + console.log(label, "function", local === report); + } else if (local instanceof Set) { + console.log(label, "set", local.size, local.has(2), local === seen); + local.add(4); + } else { + console.log(label, "other"); + } +} + +describe("first"); +current = seen; +describe("second"); +console.log("typed alias sees the write", seen.size, seen.has(4)); +current = undefined; +describe("third"); + +// A loop variable over an array whose element type does not compile. +const all: (Callbacks | number)[] = [report, seen, 7]; +for (const item of all) { + if (typeof item === "number") console.log("number", item + 1); + else if (typeof item === "function") item(3, 4); + else console.log("object", item instanceof Set); +} +all.push(9); +console.log(all.length); diff --git a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt index ed727b6048..ab79ac05e1 100644 --- a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt +++ b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt @@ -1,18 +1,16 @@ dynamic tier (tier assignment, report-only; --tier=auto) 0 functions already operate on dynamic values (0 statements) - 11 demotion requests (refusals and fences the dynamic tier would replace) + 9 demotion requests (refusals and fences the dynamic tier would replace) - 4 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) - 11 errors: 9 clear with declaration slots (L1-L2), 9 with member and expression slots too (L3-L4); 6 cascades attributed; 1 type failure stays refused + 3 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) + 9 errors: 7 clear with declaration slots (L1-L2), 7 with member and expression slots too (L3-L4); 5 cascades attributed; 1 type failure stays refused L2 field tools: Tool[] tests/coverage-fixtures/localized-failure.ts:13:11 (SC2009 component, clears 2) rejects class Session L2 param progress: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:23:31 (SC2009 component, clears 1) rejects function listAll L2 field state: any tests/coverage-fixtures/localized-failure.ts:29:3 (SC1090 dyn-field-policy, clears 1) rejects class Holder - L1 local callbacks: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:37:9 (SC2009 component, clears 1) - rejects statement class Session tests/coverage-fixtures/localized-failure.ts:12:1 (1 request) <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:49:11: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet -> function turns dynamic @@ -35,15 +33,9 @@ function listAll tests/coverage-fixtures/localized-failure.ts:23:1 (1 request, 1 site inside) <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:51:13: uses of 'listAll' inherit the blocker on its declaration -> function turns dynamic - function pick tests/coverage-fixtures/localized-failure.ts:36:1 (2 sites inside) binding progress in listAll tests/coverage-fixtures/localized-failure.ts:23:31 (1 request) <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:23:31: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function listAll turns dynamic - binding callbacks in pick tests/coverage-fixtures/localized-failure.ts:37:9 (2 requests) - <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:37:9: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function pick turns dynamic - <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:38:10: uses of 'callbacks' inherit the blocker on its declaration - -> function pick turns dynamic binding s tests/coverage-fixtures/localized-failure.ts:49:7 (1 request) <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:50:13: uses of 's' inherit the blocker on its declaration -> function turns dynamic \ No newline at end of file diff --git a/tests/harness/corpus-costs/localized-failure-held-values.ts.json b/tests/harness/corpus-costs/localized-failure-held-values.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-held-values.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/corpus-costs/localized-failure-locals.ts.json b/tests/harness/corpus-costs/localized-failure-locals.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-locals.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 3f9a60bcea..df99a4ece5 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -431,8 +431,11 @@ test("--explain-dynamic attributes each refusal to the unit the dynamic tier wou test("--explain-dynamic lists the type failures localized failure would turn into dynamic slots", async () => { const { coverage } = analyze(fixture("localized-failure.ts"), { explainDynamic: true }); const report = coverage.tiers!.localizedFailure; - expect(report.errors).toBe(11); - expect(report.typeFailures).toEqual({ degradable: 4, refused: 1 }); + // The local already degrades (stage L1); fields and parameters are + // listed as would-be slots with the uses each one rejects. + expect(coverage.dynamicSlots?.map((s) => [s.slot, s.name])).toEqual([["local", "callbacks"]]); + expect(report.errors).toBe(9); + expect(report.typeFailures).toEqual({ degradable: 3, refused: 1 }); expect( report.slots.map((s) => [ s.stage, @@ -446,11 +449,10 @@ test("--explain-dynamic lists the type failures localized failure would turn int ["L2", "field", "tools", "component", "class Session", 2], ["L2", "param", "progress", "component", "function listAll", 1], ["L2", "field", "state", "dyn-field-policy", "class Holder", 1], - ["L1", "local", "callbacks", "component", "statement", 1], ]); // The WeakRef field stays refused, so its class and the construction // that uses it stay errors. - expect(report.clearedThroughL2).toBe(9); + expect(report.clearedThroughL2).toBe(7); expect(report.remainingByCode).toEqual({ SC1090: 1, SC2020: 1 }); const relative = JSON.parse( JSON.stringify(coverage.tiers).replaceAll(repoRoot + "/", ""), @@ -460,6 +462,39 @@ test("--explain-dynamic lists the type failures localized failure would turn int ); }); +test("a local or module global whose declared type does not compile becomes a dynamic slot", async () => { + const program = join(repoRoot, "tests/corpus/localized-failure-locals.ts"); + const { coverage, sourceTexts } = analyze(program); + expect(coverage.diagnostics).toEqual([]); + expect(coverage.dynamicSlots?.map((s) => [s.slot, s.name, s.declaredType, s.code])).toEqual([ + ["global", "current", "Callbacks | undefined", "SC2009"], + ["global", "all", "(number | Callbacks)[]", "SC2009"], + ["local", "local", "Callbacks | undefined", "SC2009"], + ]); + expect(coverage.performance?.filter((d) => d.code === "SC6104")).toHaveLength(3); + expect(coveragePasses(coverage, "blockers")).toBe(true); + expect(coveragePasses(coverage, "dynamic")).toBe(true); + expect(coveragePasses(coverage, "dynamic-slots")).toBe(false); + const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }); + expect(envelope.dynamicSlots?.map((s) => [s.kind, s.name, s.line])).toEqual([ + ["global", "current", 10], + ["global", "all", 36], + ["local", "local", 14], + ]); + expect(envelope.diagnostics.filter((d) => d.code === "SC6104").map((d) => d.severity)).toEqual([ + "warning", + "warning", + "warning", + ]); + const text = renderCoverage(coverage, { sourceTexts, root: repoRoot }); + expect(text).toContain("dynamic slots 3"); + expect(text).toContain("module global 'current' (declared 'Callbacks | undefined')"); + // --tier=static-only keeps every refusal. + const strict = analyze(program, { tier: "static-only" }).coverage; + expect(strict.dynamicSlots).toBeUndefined(); + expect(strict.diagnostics.map((d) => d.code)).toContain("SC2009"); +}); + test("CLI: --tier and --explain-dynamic", () => { const scriptcCli = join(repoRoot, "packages/cli/src/main.ts"); const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-")); From 6a2c9eca213758fc36bf23663e0b017682fb5749 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:44:06 -0700 Subject: [PATCH 09/49] Record order-parity baselines for the localized-failure corpus programs Co-Authored-By: Claude Opus 5.5 --- .../tests/corpus/localized-failure-held-values.ts.json | 6 ++++++ .../tests/corpus/localized-failure-locals.ts.json | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-held-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-locals.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-held-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-held-values.ts.json new file mode 100644 index 0000000000..2327ce301e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-held-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-held-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-locals.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-locals.ts.json new file mode 100644 index 0000000000..9a45d0613b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-locals.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-locals.ts" + ], + "diags": [] +} From cd5607493e80c361eeeace337a40311da15fa19f Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 18:57:21 -0700 Subject: [PATCH 10/49] Pin the diagnostics corpus to --tier=static-only Its programs pin today's refusals; localized failure turns several of them into checked dynamic slots in the default mode. Under static-only every snapshot stays byte-identical, which is the stage's proof that the switch restores the refusals. Co-Authored-By: Claude Opus 5.5 --- tests/harness/diagnostics.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/harness/diagnostics.test.ts b/tests/harness/diagnostics.test.ts index 77fd5937da..895ed9647e 100644 --- a/tests/harness/diagnostics.test.ts +++ b/tests/harness/diagnostics.test.ts @@ -39,7 +39,14 @@ describe(`diagnostics corpus (${files.length} programs)`, () => { if (name.startsWith("checker-panic-")) process.env["SCRIPTC_CHECKER"] = "tsgo"; let result: Awaited>; try { - result = await compile(file, { outPath: join(outDir, "never"), outDir }); + // --tier=static-only: these programs pin today's refusals, which + // localized failure otherwise turns into checked dynamic slots + // (the snapshots double as its proof that static-only keeps them). + result = await compile(file, { + outPath: join(outDir, "never"), + outDir, + tier: "static-only", + }); } finally { if (checker === undefined) delete process.env["SCRIPTC_CHECKER"]; else process.env["SCRIPTC_CHECKER"] = checker; From 8b0779032551ce75f8b0ee99cc96d7434c411210 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:53:13 -0700 Subject: [PATCH 11/49] Register classes and functions with dynamic slots when a reference flushes their type failures Stage L2 of the localized-failure design. A class whose field (or parameter property) type has no static lowering, or a function or method whose parameter or return type has none, used to be rejected, and every use of it failed on its own line. Its collection deferred the diagnostics until a reachable reference flushed them into the build. That flush is now a commit point: when every deferred diagnostic is a degradable type failure at one of the declaration's slots, the failing types degrade and the collection runs again, up to 16 rounds. The declaration registers with checked dynamic slots (one SC6104 each), the reference proceeds, and emitReachable (or run) lowers the bodies it had skipped. A derived class whose base registers this way retries with it. The 'any'-typed class field refusal is lifted: a TypeScript class field whose type maps to the checked dynamic stores it, as JavaScript classes already do. A program that compiles today flushes no deferred diagnostics into its build, so nothing degrades and its output is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/diagnostics/diagnostic.ts | 3 +- .../lowering/builtins/disposable-stack.ts | 3 +- .../lowering/expressions/identifiers.ts | 8 +- .../lowering/expressions/property-access.ts | 12 +- .../lowering/expressions/relational.ts | 9 +- .../src/frontend/lowering/lower-calls.ts | 11 +- .../src/frontend/lowering/lower-classes.ts | 64 ++---- .../src/frontend/lowering/lower-exprs.ts | 8 +- .../src/frontend/lowering/lower-modules.ts | 12 +- .../src/frontend/lowering/lower-stmts.ts | 8 +- .../compiler/src/frontend/lowering/lowerer.ts | 210 +++++++++++++++--- .../src/frontend/lowering/type-failure.ts | 1 - tests/corpus/localized-failure-cycles.ts | 29 +++ .../corpus/localized-failure-declarations.ts | 64 ++++++ tests/corpus/localized-failure-pi-shapes.ts | 37 +++ .../coverage-explain-localized-failure.txt | 41 +--- .../localized-failure-cycles.ts.json | 1 + .../localized-failure-declarations.ts.json | 1 + .../localized-failure-pi-shapes.ts.json | 1 + tests/harness/coverage.test.ts | 32 +-- 20 files changed, 388 insertions(+), 167 deletions(-) create mode 100644 tests/corpus/localized-failure-cycles.ts create mode 100644 tests/corpus/localized-failure-declarations.ts create mode 100644 tests/corpus/localized-failure-pi-shapes.ts create mode 100644 tests/harness/corpus-costs/localized-failure-cycles.ts.json create mode 100644 tests/harness/corpus-costs/localized-failure-declarations.ts.json create mode 100644 tests/harness/corpus-costs/localized-failure-pi-shapes.ts.json diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 534e03c076..44bd927447 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -153,8 +153,7 @@ export type TypeFailureFamily = | "component" | "stdlib-nominal" | "record-member" - | "unsupported" - | "dyn-field-policy"; + | "unsupported"; /** A type failure's structured payload: what the commit points of the * localized-failure design inspect (PoisonError.failure carries the same diff --git a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts index 375c3f1e72..866358b068 100644 --- a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts +++ b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts @@ -73,8 +73,7 @@ export function lowerDisposableStackUseCall( if (valueT?.kind !== "object") return null; const loc = locOf(call); const className = valueT.className; - const info = lowerer.classes.get(className); - if (!info) lowerer.flushDeferredClass(className); + const info = lowerer.classes.get(className) ?? lowerer.flushDeferredClass(className); const found = info ? lowerer.findMethodOn(info, "sym:dispose") : null; if (!found) return null; if (found.sig.params.length !== 0) { diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 62b2c79601..8519c41737 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -318,10 +318,12 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // that does not exist. The blocked-binding cascade names the use; // without this the validator's registration check ICEs on the live // reference (`@this {T}` inference — signature 07). - const unregistered = lowerer.unregisteredClassOf(g.type, new Set()); + let unregistered = lowerer.unregisteredClassOf(g.type, new Set()); + // The class's own rejection is the root cause the cascade names + // (unless the flush registers it with dynamic slots). + if (unregistered !== null && lowerer.flushDeferredClass(unregistered) !== undefined) + unregistered = lowerer.unregisteredClassOf(g.type, new Set()); if (unregistered !== null) { - // The class's own rejection is the root cause the cascade names. - lowerer.flushDeferredClass(unregistered); lowerer.pushDiag(blockedBindingUseDiag(expr.text, loc)); throw new PoisonError(); } diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 6466857a57..ae4eea8ad5 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -3644,13 +3644,13 @@ export function classFieldTarget( ) { return { container: "dynamic", obj: represented, field: fieldName, fieldType: DYN }; } - const info = lowerer.classes.get(receiverType.className); - if (!info) { - // A receiver typed as a class whose collection deferred: the - // deferred diagnostics are what explains the miss. + // A receiver typed as a class whose collection deferred: the deferred + // diagnostics are what explains the miss (unless the flush registers + // the class with dynamic slots, retryDeferred). + const info = + lowerer.classes.get(receiverType.className) ?? lowerer.flushDeferredClass(receiverType.className); - return null; - } + if (!info) return null; const fieldType = info.fields.get(fieldName); const lowerObjectReceiver = (): IrExpr => { let obj = represented; diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index b323162c03..89817e0574 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -535,12 +535,13 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc }; } if (rhsClassval?.kind === "classval" && !caught) { - const targetInfo = lowerer.classes.get(rhsClassval.className); + const targetInfo = + lowerer.classes.get(rhsClassval.className) ?? + lowerer.flushDeferredClass(rhsClassval.className); if (!targetInfo) { // The type world names a class the lowering never registered - // (a fenced class expression, a deferred declaration): flush - // its diagnostics and poison the test site, never an ICE. - lowerer.flushDeferredClass(rhsClassval.className); + // (a fenced class expression, a deferred declaration): its + // diagnostics flushed above; poison the test site, never an ICE. lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 0a845b3bde..964cb296c4 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10229,8 +10229,8 @@ export function lowerObjectMethodCall( ? lowerer.coerceInto(access.expression, receiver, receiverIr) : receiver; }; - const info = lowerer.classes.get(receiverIr.className); - if (!info) lowerer.flushDeferredClass(receiverIr.className); + const info = + lowerer.classes.get(receiverIr.className) ?? lowerer.flushDeferredClass(receiverIr.className); const found = info ? lowerer.findMethodOn(info, access.name.text) : null; // The zlib stream members (flush/params/close/reset) are declared on // the zlib classes; their receivers are runtime Transform streams. @@ -10583,11 +10583,8 @@ function lowerUnionObjectMethodCall( found: NonNullable>; }[] = []; for (const arm of def.arms as (IrType & { kind: "object" })[]) { - const info = lowerer.classes.get(arm.className); - if (!info) { - lowerer.flushDeferredClass(arm.className); - return null; - } + const info = lowerer.classes.get(arm.className) ?? lowerer.flushDeferredClass(arm.className); + if (!info) return null; const found = lowerer.findMethodOn(info, method); if (!found) return null; if (found.sig.abstract === true && !lowerer.overrideBelow(info, method)) return null; diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 2c0145c340..f15e0dc86f 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -2297,11 +2297,17 @@ export function collectClassShapeInner( base = named; if (!base) { if (adoptCallableBase(t.expression)) continue; - lowerer.unsupported( + // A base whose own collection deferred can still register later + // (a flush retry with dynamic slots): the derived class retries + // with it (Lowerer.retryDeferred). + const diag = unsupportedDiag( "SC1090", - t, + locOf(t), `extending classes not declared in the program ('${t.expression.text}')`, ); + lowerer.retriableDiags.add(diag); + lowerer.pushDiag(diag); + throw new PoisonError(); } } @@ -2535,18 +2541,6 @@ export function collectClassShapeInner( if (initializerSymbol && (initializerSymbol.flags & ts.SymbolFlags.Enum) !== 0) continue; const type = lowerer.irTypeOf(member.name); if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); - if ( - type.kind === "dyn" && - !isJsSourceFile(ts.getSourceFileOfNode(member)) && - !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) && - (member.type !== undefined || - (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) - ) { - lowerer.dynFieldPolicyFence( - member.name, - `${dynFieldTypeSpelling(lowerer, member.name)}-typed static fields`, - ); - } staticFields.push({ name: member.name.text, type, @@ -3050,19 +3044,9 @@ export function collectClassShapeInner( if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type)) type = DYN; } if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); - // Bundled JS declares fields without annotations before assigning - // them in the constructor. Use the same native checked-dynamic - // storage as implicit constructor-assigned JS fields. - if ( - type.kind === "dyn" && - !isJsSourceFile(ts.getSourceFileOfNode(member)) && - !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) - ) { - lowerer.dynFieldPolicyFence( - member.name, - `${dynFieldTypeSpelling(lowerer, member.name)}-typed class fields`, - ); - } + // A field whose type maps to the checked dynamic (`any`, an + // option bag, a degraded type) stores it natively, as JavaScript + // classes and constructor-assigned fields do. const undefinedInitializer = type.kind === "dyn" && !member.initializer ? locOf(member) : undefined; if (fields.has(member.name.text)) { @@ -3193,13 +3177,6 @@ export function collectClassShapeInner( const shape = lowerer.paramShape(p); const type = shape.bodyType ?? shape.type; if (type.kind === "void") lowerer.badType(p.name, lowerer.typeOf(p.name)); - // The class-field dyn rule verbatim (KEEP NARROW). - if (type.kind === "dyn" && !lowerer.classFieldStoresDyn(lowerer.typeOf(p))) { - lowerer.dynFieldPolicyFence( - p.name, - `${dynFieldTypeSpelling(lowerer, p)}-typed class fields`, - ); - } // `override x` (and any same-named inherited member) would // redeclare a base slot — the declared-field rule verbatim. if (fields.has(name)) { @@ -8942,14 +8919,6 @@ export function throwingSetterFn(lowerer: Lowerer, info: ClassInfo, prop: string }; } -/** The declared type a refused checked-dynamic class field names in its - * diagnostic (`'any'`, `'WeakMap'`): `unknown` and - * `object` fields store checked-dynamic values, so the refusal is about - * whatever else the declaration spells. */ -function dynFieldTypeSpelling(lowerer: Lowerer, node: ts.Node): string { - return `'${lowerer.checker.typeToString(lowerer.typeOf(node))}'`; -} - /** The base a constructor chain actually CALLS into: generic FAMILIES are * never constructed (no `%.constructor` exists), so an * instantiation's construction-relevant base is the family's own base — @@ -11312,12 +11281,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { ); info = lowerer.classes.get(calleeT.className); } + // The TYPE world names a class the lowering never registered — a + // fenced class expression, an abstract/deferred declaration: flush + // its own diagnostics (they tell the real story, unless the flush + // registers it with dynamic slots) and poison this construction + // site, never an ICE. + if (!info) info = lowerer.flushDeferredClass(calleeT.className); if (!info) { - // The TYPE world names a class the lowering never registered — - // a fenced class expression, an abstract/deferred declaration: - // flush its own diagnostics (they tell the real story) and - // poison this construction site, never an ICE. - lowerer.flushDeferredClass(calleeT.className); lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 006975085b..7213a37c81 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -3136,11 +3136,9 @@ export function symbolFieldInfo( if (lowerer.chainBlocked(expr)) return null; const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); if (receiverIr?.kind !== "object") return null; - const info = lowerer.classes.get(receiverIr.className); - if (!info) { - lowerer.flushDeferredClass(receiverIr.className); - return null; - } + const info = + lowerer.classes.get(receiverIr.className) ?? lowerer.flushDeferredClass(receiverIr.className); + if (!info) return null; const key = classSymbolKeyOf(lowerer, expr.argumentExpression); if (!key) return null; const field = info.symbolFields?.get(key.identity); diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index dd0467bd0f..8aa3ddb2c3 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -1036,13 +1036,11 @@ export function moduleArtifacts( } while (pendingClasses.length > 0) { const name = pendingClasses.pop()!; - const info = lowerer.classes.get(name); - if (!info) { - // Referenced by an emitted type but never registered: collection - // deferred its diagnostics — a reached reference makes them count. - lowerer.flushDeferredClass(name); - continue; - } + // Referenced by an emitted type but never registered: collection + // deferred its diagnostics — a reached reference makes them count + // (or registers the class with dynamic slots, retryDeferred). + const info = lowerer.classes.get(name) ?? lowerer.flushDeferredClass(name); + if (!info) continue; for (const field of info.def.fields) visitType(field.type); if (info.base) visitClass(info.base.def.name); if (info.def.genericOf) visitClass(info.def.genericOf); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 7d5273e074..3cce518173 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -417,8 +417,8 @@ function cleanupCall( ): { call: IrExpr; async: boolean } | null { const loc = locOf(node); if (value.type.kind === "object") { - const info = lowerer.classes.get(value.type.className); - if (!info) lowerer.flushDeferredClass(value.type.className); + const info = + lowerer.classes.get(value.type.className) ?? lowerer.flushDeferredClass(value.type.className); const asyncFound = awaited && info ? lowerer.findMethodOn(info, "sym:asyncDispose") : null; const syncFound = info ? lowerer.findMethodOn(info, "sym:dispose") : null; const member = asyncFound ? "sym:asyncDispose" : syncFound ? "sym:dispose" : null; @@ -3291,8 +3291,8 @@ export function lowerBindingPattern( // chain declares keep named fences (a detached method would lose its // receiver without an error; JS binds the prototype function). if (srcType.kind === "object") { - const info = lowerer.classes.get(srcType.className); - if (!info) lowerer.flushDeferredClass(srcType.className); + const info = + lowerer.classes.get(srcType.className) ?? lowerer.flushDeferredClass(srcType.className); if (info) { for (const el of pattern.elements.nodes) { if (el.name === undefined) continue; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 6b1174d68e..00b62dcc41 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -730,6 +730,19 @@ export interface DynamicSlot { reason: string; } +/** Localized failure: rounds of the declaration flush retry. */ +const MAX_DECLARATION_RETRIES = 16; + +/** The slot kinds the declaration flush retry degrades (stage L2). */ +const DECLARATION_SLOTS: ReadonlySet = new Set([ + "field", + "param", + "return", + "local", + "global", + "loop", +]); + export class PoisonError extends Error { /** Set when a type with no static lowering poisoned a slot (badType and * the dyn class field policy): what failed, for the localized-failure @@ -971,6 +984,10 @@ export interface LowererMode { /** Symbols whose deferred diagnostics reachable emit already flushed — * the remainder must not report them a second time. */ alreadyFlushed?: ReadonlySet; + /** Localized failure: the types reachable emit degraded (the coverage + * remainder maps them alike, so the declarations reachable emit + * registered with dynamic slots collect there too). */ + degradedTypes?: ReadonlyMap; /** LowerOptions.externalTypes, threaded through every lowering pass. */ externalTypes?: ReadonlyMap; /** LowerOptions.externalTypeSpecifiersByFile, shared by every pass. */ @@ -1378,6 +1395,7 @@ export function lowerToIr( reachable, remainder: true, alreadyFlushed: resultLowerer.flushedSymbols, + degradedTypes: resultLowerer.degradedTypes, inlineRequireCycleCalls, targetPlatform, ffiImports, @@ -3761,6 +3779,22 @@ export class Lowerer { readonly degradedTypes = new Map(); /** The dynamic slots the commit points created: one SC6104 each. */ readonly dynamicSlots: DynamicSlot[] = []; + /** Bodies run() skipped because their declaration had not registered. */ + readonly skippedBodies = new Set(); + /** Declarations retryDeferred registered, in order: run() lowers the + * bodies it had skipped. */ + readonly retriedDeclarations: ts.Node[] = []; + /** Deferred declarations' collections, for the flush retry + * (retryDeferred). */ + readonly deferredCollectors = new Map< + ts.Symbol, + { symbolOf: () => ts.Symbol | undefined; collect: () => void } + >(); + /** Collection diagnostics that a flush retry may simply run again (a + * derived class whose base had not registered yet). */ + readonly retriableDiags = new WeakSet(); + /** The checker type behind each type failure badType committed. */ + readonly failedTypes = new WeakMap(); /** True while collectProgram runs: resolution helpers must not flush * deferred diagnostics (collection itself resolves symbols — extends * clauses — and collection order must not decide what reports). */ @@ -3814,12 +3848,12 @@ export class Lowerer { /** Bumps the per-file attribution counter (no-op unless provenance is * active and this is the emit/discovery lane — mirror the CALLER's * suppressStats guard, this method only gates remainder). */ - bumpFileStat(file: string, kind: "total" | "failed"): void { + bumpFileStat(file: string, kind: "total" | "failed", by = 1): void { if (this.remainder || !provenanceActive()) return; let s = this.statsByFile.get(file); if (!s) this.statsByFile.set(file, (s = { total: 0, failed: 0 })); - if (kind === "total") s.total += 1; - else if (kind === "failed") s.failed += 1; + if (kind === "total") s.total += by; + else if (kind === "failed") s.failed += by; } /** The program holds the declaration-typed boundary's projection @@ -3843,6 +3877,7 @@ export class Lowerer { ) { // The mode's options, sorted by name; the values derived from them follow. this.alreadyFlushed = mode.alreadyFlushed ?? new Set(); + for (const [id, degraded] of mode.degradedTypes ?? []) this.degradedTypes.set(id, degraded); this.externalTypes = mode.externalTypes ?? new Map(); this.ffiBindingSymbols = mode.ffiBindingSymbols ?? null; this.ffiImports = mode.ffiImports ?? []; @@ -8151,9 +8186,12 @@ export class Lowerer { if (sig && !this.wantBody(sig.name)) continue; const fn = this.lowerFunction(decl); if (fn) functions.push(fn); - else if (this.countsSkips()) { - this.countSkippedBody(decl); - if (this.remainder) this.reportBlockedBody(decl); + else { + this.skippedBodies.add(decl); + if (this.countsSkips()) { + this.countSkippedBody(decl); + if (this.remainder) this.reportBlockedBody(decl); + } } } for (const decl of fp.classDecls) { @@ -8162,7 +8200,10 @@ export class Lowerer { functions.push( ...this.withClosureRoot(info.def.name, () => this.lowerClassMembers(info)), ); - else if (this.countsSkips()) this.countSkippedBody(decl); + else { + this.skippedBodies.add(decl); + if (this.countsSkips()) this.countSkippedBody(decl); + } } } @@ -8197,15 +8238,38 @@ export class Lowerer { let gc = 0; let gi = 0; let es = 0; + let rd = 0; const classDispatch = new ClassDynamicDispatch(); let dispatchChanged = !this.remainder; while ( dispatchChanged || + rd < this.retriedDeclarations.length || ec < this.exprClasses.length || gc < this.genericClassInstances.length || gi < this.instantiationQueue.length || es < this.emitSpecQueue.length ) { + // Localized failure: declarations a flush registered with dynamic + // slots (retryDeferred) after the loops above skipped their bodies. + while (rd < this.retriedDeclarations.length) { + const decl = this.retriedDeclarations[rd++]!; + if (!this.skippedBodies.delete(decl)) continue; + if (this.countsSkips()) this.uncountSkippedBody(decl); + if (ts.isFunctionDeclaration(decl)) { + const fn = this.lowerFunction(decl); + if (fn) functions.push(fn); + else { + this.skippedBodies.add(decl); + if (this.countsSkips()) this.countSkippedBody(decl); + } + } else if (ts.isClassDeclaration(decl)) { + const info = this.classes.get(this.classNamer(decl)); + if (info) + functions.push( + ...this.withClosureRoot(info.def.name, () => this.lowerClassMembers(info)), + ); + } + } while (ec < this.exprClasses.length) { functions.push(...this.lowerClassMembers(this.exprClasses[ec++]!)); } @@ -8485,6 +8549,22 @@ export class Lowerer { * percentage would overstate how much of the program compiles. Count * every statement the body holds as attempted and failed, in addition to * the skipped-function tally. */ + /** countSkippedBody undone: a flush registered the declaration after + * its body was skipped, and the body lowers now. */ + private uncountSkippedBody(decl: ts.Node): void { + this.stats.functionsSkipped--; + if (this.suppressStats) return; + const n = countBodyStatements(decl); + if (n === 0) return; + this.stats.statementsTotal -= n; + this.stats.statementsFailed -= n; + const file = ts.getSourceFileOfNode(decl).fileName; + for (let i = 0; i < n; i++) { + this.bumpFileStat(file, "total", -1); + this.bumpFileStat(file, "failed", -1); + } + } + private countSkippedBody(decl: ts.Node): void { this.stats.functionsSkipped++; if (this.suppressStats) return; @@ -8808,6 +8888,27 @@ export class Lowerer { register(`%${cName}.set:${prop}`, [], () => this.throwingSetterFn(info, prop)); } }; + // Localized failure: a declaration a flush registered with dynamic + // slots (retryDeferred) registers its units like a class expression. + this.onDeclarationRegistered = (decl: ts.Node): void => { + if (ts.isFunctionDeclaration(decl)) { + if (!decl.body) return; + const symbol = declSymbolOf(this, decl); + const sig = symbol ? this.fnSigsBySymbol.get(symbol) : undefined; + if (!sig || units.has(sig.name)) return; + units.set(sig.name, { + order: unitOrder++, + roots: functionRoots(decl), + lower: () => this.lowerFunction(decl), + }); + if (reachable.has(sig.name)) queue.push(sig.name); + metadataPriority.set(sig.name, [3, expressionMetadataOrder++]); + } else if (ts.isClassDeclaration(decl)) { + const info = this.classes.get(this.classNamer(decl)); + if (info && !info.generic && !units.has(`%${info.def.name}.constructor`)) + this.onExprClassCollected?.(info); + } + }; const onEdge = this.onEdge; for (const name of collectionEdges) onEdge(name); // Generic instances queued by the bodies above lower here (an instance @@ -9314,6 +9415,7 @@ export class Lowerer { ): never { const failure = typeFailureOf(node, this.checker.typeToString(type), family); diag.typeFailure = failure; + this.failedTypes.set(failure, type); this.pushDiag(diag); throw new PoisonError(failure, type); } @@ -9332,7 +9434,7 @@ export class Lowerer { const failure = e.failure; const type = e.failedType; if (failure === undefined || type === undefined) return null; - if (!failure.degradable || failure.family === "dyn-field-policy") return null; + if (!failure.degradable) return null; if (!slots.has(failure.slot)) return null; const id = (type as { id?: number }).id; // Already degraded and still failing: no progress, commit as today. @@ -9376,21 +9478,6 @@ export class Lowerer { if (!seen) this.dynamicSlots.push(slot); } - /** The SC1090 refusal of checked-dynamic class fields in TypeScript - * (classFieldStoresDyn): a policy fence, not a missing representation, - * so its failure is recorded as degradable. */ - dynFieldPolicyFence(node: ts.Node, feature: string): never { - const diag = unsupportedDiag("SC1090", locOf(node), feature); - const failure = typeFailureOf( - node, - this.checker.typeToString(this.typeOf(node)), - "dyn-field-policy", - ); - diag.typeFailure = failure; - this.pushDiag(diag); - throw new PoisonError(failure, this.typeOf(node)); - } - badType(node: ts.Node, type: ts.Type): never { const widened = this.checker.getBaseTypeOfLiteralType(type); // Types declared by the ADOPTED @types/node (Buffer, NodeJS.Timeout, @@ -12252,6 +12339,7 @@ export class Lowerer { const list = this.deferredDiags.get(symbol) ?? []; list.push(...sink); this.deferredDiags.set(symbol, list); + this.deferredCollectors.set(symbol, { symbolOf, collect }); return symbol; } finally { this.diagSink = previousSink; @@ -12302,10 +12390,13 @@ export class Lowerer { return; } if (this.diagSink === null) { + if (this.retryDeferred(symbol, diags)) return; + // A failed retry leaves the last attempt's diagnostics deferred. + const committed = this.deferredDiags.get(symbol) ?? diags; this.deferredDiags.delete(symbol); this.flushedSymbols.add(symbol); - this.noteRejected(symbol, diags); - for (const d of diags) this.pushDiag(d); + this.noteRejected(symbol, committed); + for (const d of committed) this.pushDiag(d); this.touchRejected(symbol); return; } @@ -12331,9 +12422,73 @@ export class Lowerer { if (list.length === 0) this.deferredDiags.delete(symbol); } - flushDeferredClass(className: string): void { + /** Flushes a deferred class known by its IR name, and answers the class + * when the flush registered it (retryDeferred). */ + flushDeferredClass(className: string): ClassInfo | undefined { const symbol = this.deferredClassByName.get(className); if (symbol) this.flushDeferred(symbol); + return this.classes.get(className); + } + + /** Localized failure's declaration commit point (design §3.3): a + * reference is flushing a deferred declaration into the build. When + * every deferred diagnostic is a degradable type failure at one of the + * declaration's slots, the failing types degrade and the collection + * runs again (up to MAX_DECLARATION_RETRIES rounds, each degrading the + * types the previous round still failed on). On success the + * declaration registers, each degraded slot is an SC6104, and nothing + * reports. Otherwise the last round's diagnostics stay deferred for the + * caller to commit. */ + private retryDeferred(symbol: ts.Symbol, diags: readonly ScrDiagnostic[]): boolean { + if (this.tierMode !== "auto") return false; + const collector = this.deferredCollectors.get(symbol); + if (collector === undefined) return false; + const slots: DynamicSlot[] = []; + let current = diags; + for (let round = 0; round < MAX_DECLARATION_RETRIES; round++) { + const failing: { diag: ScrDiagnostic; failure: TypeFailure; id: number }[] = []; + let retriable = false; + for (const diag of current) { + if (this.retriableDiags.has(diag)) { + retriable = true; + continue; + } + const failure = diag.typeFailure; + const type = failure === undefined ? undefined : this.failedTypes.get(failure); + const id = (type as { id?: number } | undefined)?.id; + if ( + failure === undefined || + id === undefined || + !failure.degradable || + !DECLARATION_SLOTS.has(failure.slot) || + this.degradedTypes.has(id) + ) + return false; + failing.push({ diag, failure, id }); + } + // A round makes progress by degrading a type, or (once) by running + // a collection that only waited for another declaration. + if (failing.length === 0 && !(retriable && round === 0)) return false; + for (const { diag, failure, id } of failing) { + this.degradedTypes.set(id, { lenient: DYN, code: diag.code, reason: diag.message }); + slots.push({ loc: diag.loc, failure, code: diag.code, reason: diag.message }); + } + this.deferredDiags.delete(symbol); + if (this.collectDeferring(collector.symbolOf, collector.collect) === null) { + this.deferredCollectors.delete(symbol); + for (const [name, deferred] of this.deferredClassByName) + if (deferred === symbol) this.deferredClassByName.delete(name); + for (const slot of slots) this.noteDynamicSlot(slot); + for (const decl of symbol.declarations) + if (ts.isFunctionDeclaration(decl) || ts.isClassDeclaration(decl)) { + this.retriedDeclarations.push(decl); + this.onDeclarationRegistered?.(decl); + } + return true; + } + current = this.deferredDiags.get(symbol) ?? []; + } + return false; } collectSignature(decl: ts.FunctionDeclaration): void { @@ -12612,6 +12767,9 @@ export class Lowerer { * bodies as worklist units (the units map is otherwise built before * lowering starts). Null in the emit pass. */ onExprClassCollected: ((info: ClassInfo) => void) | null = null; + /** emitReachable's unit registration for a declaration retryDeferred + * registered after collection. */ + onDeclarationRegistered: ((decl: ts.Node) => void) | null = null; /** Mixin functions (`(Base: T) => class extends Base {…}`) by their * function-like node: recognized shape, or null for checked diff --git a/packages/compiler/src/frontend/lowering/type-failure.ts b/packages/compiler/src/frontend/lowering/type-failure.ts index 3502a9bbf7..1267779735 100644 --- a/packages/compiler/src/frontend/lowering/type-failure.ts +++ b/packages/compiler/src/frontend/lowering/type-failure.ts @@ -34,7 +34,6 @@ const DEGRADABLE_FAMILIES: ReadonlySet = new Set dynamic field -> capsule -> instance). +type Peer = Link | Map string> | AbortSignal | (() => number); + +class Link { + id: number; + peer: Peer | undefined; + constructor(id: number) { + this.id = id; + this.peer = undefined; + } + peerId(): number { + const p = this.peer; + return p instanceof Link ? p.id : -1; + } +} + +let total = 0; +for (let i = 0; i < 20000; i++) { + const a = new Link(i); + const b = new Link(i + 1); + a.peer = b; + b.peer = a; + total += a.peerId() - b.peerId(); +} +const keep = new Link(7); +keep.peer = () => keep.id * 2; +const f = keep.peer; +console.log(total, typeof f === "function" ? f() : 0, keep.peerId()); diff --git a/tests/corpus/localized-failure-declarations.ts b/tests/corpus/localized-failure-declarations.ts new file mode 100644 index 0000000000..c4acdfbaa7 --- /dev/null +++ b/tests/corpus/localized-failure-declarations.ts @@ -0,0 +1,64 @@ +// Localized failure, stage L2: fields, parameters and returns whose +// declared type has no static lowering become checked dynamic slots, so +// their class or function still compiles. +interface ToolDefinition { + name: string; + execute(id: string, params: any): string; +} +type Progress = (done: number, total: number) => void; +type ProgressOrSignal = Progress | AbortSignal | Map string> | Set; + +class Session { + // A field: the class registers, the field holds the array by reference. + private tools: ToolDefinition[] = []; + state: any = { count: 0 }; + readonly label: string; + constructor(label: string) { + this.label = label; + } + add(tool: ToolDefinition): void { + this.tools.push(tool); + } + run(id: string, params: unknown): string[] { + const out: string[] = []; + for (const tool of this.tools) out.push(tool.execute(id, params)); + this.state.count = this.state.count + 1; + return out; + } + count(): number { + return this.tools.length; + } + // A return slot. + first(): ToolDefinition | ProgressOrSignal | undefined { + return this.tools[0]; + } +} + +// A parameter: the function registers and is called from typed code. +function listAll(dir: string, onProgress?: ProgressOrSignal): number { + if (typeof onProgress === "function") onProgress(1, dir.length); + else if (onProgress instanceof Set) return onProgress.size; + return dir.length; +} + +// A base class with a dynamic parameter, and an override. +class Base { + notify(target: ProgressOrSignal | undefined): string { + return target === undefined ? "base:none" : "base:" + typeof target; + } +} +class Derived extends Base { + override notify(target: ProgressOrSignal | undefined): string { + return "derived/" + super.notify(target); + } +} + +const echo: ToolDefinition = { name: "echo", execute: (id, params) => `${id}:${JSON.stringify(params)}` }; +const s = new Session("main"); +s.add(echo); +s.add({ name: "len", execute: (id) => String(id.length) }); +console.log(s.label, s.count(), s.run("abc", { k: 1 }).join(" | "), s.state.count); +console.log("same tool", s.first() === echo); +console.log(listAll("hello"), listAll("hello", (d, t) => console.log("progress", d, t)), listAll("x", new Set([1, 2]))); +const objs: Base[] = [new Base(), new Derived()]; +for (const o of objs) console.log(o.notify(undefined), o.notify((a, b) => a + b)); diff --git a/tests/corpus/localized-failure-pi-shapes.ts b/tests/corpus/localized-failure-pi-shapes.ts new file mode 100644 index 0000000000..9879606eb3 --- /dev/null +++ b/tests/corpus/localized-failure-pi-shapes.ts @@ -0,0 +1,37 @@ +// Localized failure, stage L2: pi-shaped declarations. +interface Token { + text: string; +} +interface AgentMessage { + role: "user" | "assistant"; + content: string[]; +} +type CreateRuntime = (cwd: string) => Promise<{ session: Session; extra: Map string> | AbortSignal }>; + +class Session { + messages: AgentMessage[] = []; + // A field whose union mentions a stdlib type with no lowering: the class + // registers; constructing a WeakRef would still be refused. + cachedTokens?: WeakRef<{ source: string; tokens: Token[] }> | undefined; + rendererState: any = {}; + effectState: Record = {}; + private readonly createRuntime: CreateRuntime | undefined; + constructor(createRuntime: CreateRuntime | undefined) { + this.createRuntime = createRuntime; + } + hasRuntime(): boolean { + return this.createRuntime !== undefined; + } + clear(): AgentMessage[] { + // An inferred return over a spread clone. + return this.messages.map((message) => ({ ...message, content: [] })); + } +} + +const s = new Session(undefined); +const withRuntime = new Session(async (cwd) => ({ session: s, extra: new Map() })); +s.messages.push({ role: "user", content: ["hi"] }, { role: "assistant", content: ["yo", "!"] }); +s.rendererState.mode = "fast"; +s.effectState["k"] = 3; +console.log(s.hasRuntime(), s.cachedTokens === undefined, s.rendererState.mode, s.effectState["k"]); +console.log(JSON.stringify(s.clear()), s.messages[1]!.content.length, withRuntime.hasRuntime()); diff --git a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt index ab79ac05e1..40486bb10d 100644 --- a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt +++ b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt @@ -1,41 +1,18 @@ dynamic tier (tier assignment, report-only; --tier=auto) - 0 functions already operate on dynamic values (0 statements) + 4 functions already operate on dynamic values (3 statements) + Holder.constructor tests/coverage-fixtures/localized-failure.ts:28:1: dynObjLit at tests/coverage-fixtures/localized-failure.ts:29:16 + Session.constructor tests/coverage-fixtures/localized-failure.ts:12:1: dynArrLit at tests/coverage-fixtures/localized-failure.ts:13:27 + Session.count tests/coverage-fixtures/localized-failure.ts:14:3: %dyn.class.property at tests/coverage-fixtures/localized-failure.ts:15:12 + %dyn.class.property.0 tests/coverage-fixtures/localized-failure.ts:15:12: %dyn.class.readPropertiesOrEmpty at tests/coverage-fixtures/localized-failure.ts:15:12 - 9 demotion requests (refusals and fences the dynamic tier would replace) + 2 demotion requests (refusals and fences the dynamic tier would replace) - 3 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) - 9 errors: 7 clear with declaration slots (L1-L2), 7 with member and expression slots too (L3-L4); 5 cascades attributed; 1 type failure stays refused - L2 field tools: Tool[] tests/coverage-fixtures/localized-failure.ts:13:11 (SC2009 component, clears 2) - rejects class Session - L2 param progress: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:23:31 (SC2009 component, clears 1) - rejects function listAll - L2 field state: any tests/coverage-fixtures/localized-failure.ts:29:3 (SC1090 dyn-field-policy, clears 1) - rejects class Holder - class Session tests/coverage-fixtures/localized-failure.ts:12:1 (1 request) - <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:49:11: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet - -> function turns dynamic - class Holder tests/coverage-fixtures/localized-failure.ts:28:1 (1 request) - <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:52:13: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet - -> function turns dynamic + 0 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) + 2 errors: 0 clear with declaration slots (L1-L2), 0 with member and expression slots too (L3-L4); 1 cascade attributed; 1 type failure stays refused class Cache tests/coverage-fixtures/localized-failure.ts:42:1 (1 request) <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:53:22: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet -> function turns dynamic - field Session.tools tests/coverage-fixtures/localized-failure.ts:13:3 (1 request) - <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:13:11: values of type 'Tool[]' cannot be compiled: the array shape is supported, but its element type 'Tool' does not compile - -> function turns dynamic - field Holder.state tests/coverage-fixtures/localized-failure.ts:29:3 (1 request) - <- SC1090 operation at tests/coverage-fixtures/localized-failure.ts:29:3: 'any'-typed class fields are not supported yet - -> function turns dynamic field Cache.tokens tests/coverage-fixtures/localized-failure.ts:43:3 (1 request) <- SC2020 operation at tests/coverage-fixtures/localized-failure.ts:43:3: 'WeakRef' is part of the standard library types but has no scriptc lowering yet -> function turns dynamic - function tests/coverage-fixtures/localized-failure.ts:1:1 (8 sites inside) - function listAll tests/coverage-fixtures/localized-failure.ts:23:1 (1 request, 1 site inside) - <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:51:13: uses of 'listAll' inherit the blocker on its declaration - -> function turns dynamic - binding progress in listAll tests/coverage-fixtures/localized-failure.ts:23:31 (1 request) - <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:23:31: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function listAll turns dynamic - binding s tests/coverage-fixtures/localized-failure.ts:49:7 (1 request) - <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:50:13: uses of 's' inherit the blocker on its declaration - -> function turns dynamic \ No newline at end of file + function tests/coverage-fixtures/localized-failure.ts:1:1 (2 sites inside) \ No newline at end of file diff --git a/tests/harness/corpus-costs/localized-failure-cycles.ts.json b/tests/harness/corpus-costs/localized-failure-cycles.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-cycles.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/corpus-costs/localized-failure-declarations.ts.json b/tests/harness/corpus-costs/localized-failure-declarations.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-declarations.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/corpus-costs/localized-failure-pi-shapes.ts.json b/tests/harness/corpus-costs/localized-failure-pi-shapes.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-pi-shapes.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index df99a4ece5..002a84f003 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -431,28 +431,18 @@ test("--explain-dynamic attributes each refusal to the unit the dynamic tier wou test("--explain-dynamic lists the type failures localized failure would turn into dynamic slots", async () => { const { coverage } = analyze(fixture("localized-failure.ts"), { explainDynamic: true }); const report = coverage.tiers!.localizedFailure; - // The local already degrades (stage L1); fields and parameters are - // listed as would-be slots with the uses each one rejects. - expect(coverage.dynamicSlots?.map((s) => [s.slot, s.name])).toEqual([["local", "callbacks"]]); - expect(report.errors).toBe(9); - expect(report.typeFailures).toEqual({ degradable: 3, refused: 1 }); - expect( - report.slots.map((s) => [ - s.stage, - s.slot, - s.name, - s.family, - s.rejects?.split(" @")[0], - s.cascade, - ]), - ).toEqual([ - ["L2", "field", "tools", "component", "class Session", 2], - ["L2", "param", "progress", "component", "function listAll", 1], - ["L2", "field", "state", "dyn-field-policy", "class Holder", 1], + // Locals, fields and parameters degrade (stages L1 and L2), and the + // 'any' field compiles; only the refused WeakRef field and its + // construction remain. + expect(coverage.dynamicSlots?.map((s) => [s.slot, s.name])).toEqual([ + ["field", "tools"], + ["param", "progress"], + ["local", "callbacks"], ]); - // The WeakRef field stays refused, so its class and the construction - // that uses it stay errors. - expect(report.clearedThroughL2).toBe(7); + expect(report.errors).toBe(2); + expect(report.typeFailures).toEqual({ degradable: 0, refused: 1 }); + expect(report.slots).toEqual([]); + expect(report.cascades).toBe(1); expect(report.remainingByCode).toEqual({ SC1090: 1, SC2020: 1 }); const relative = JSON.parse( JSON.stringify(coverage.tiers).replaceAll(repoRoot + "/", ""), From f0eb4271522e90c6a8a5100f0effb8845fef1280 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:56:50 -0700 Subject: [PATCH 12/49] Retry declarations a probe flushed at the statement that commits them; keep the dyn field policy outside reached classes A flush inside a capture buffer cannot retry (a probe that later declines must see today's answer), so its copies of the declaration's diagnostics reach the build when the statement commits. That statement commit point now reopens such a declaration: when its diagnostics are retriable, they leave the build, the declaration retries, and the statement lowers again. Probe copies of a declaration that already registered are dropped. The 'any'-typed class field refusal stays a fence at collection and is lifted by the flush retry, so only reached classes change; the coverage remainder registers the declarations reachable emit registered, and maps the degraded types alike. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/diagnostics/diagnostic.ts | 3 +- .../src/frontend/lowering/lower-classes.ts | 51 ++++++++- .../src/frontend/lowering/lower-stmts.ts | 13 ++- .../compiler/src/frontend/lowering/lowerer.ts | 104 +++++++++++++++++- .../src/frontend/lowering/type-failure.ts | 1 + .../coverage-explain-localized-failure.txt | 2 +- 6 files changed, 160 insertions(+), 14 deletions(-) diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 44bd927447..534e03c076 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -153,7 +153,8 @@ export type TypeFailureFamily = | "component" | "stdlib-nominal" | "record-member" - | "unsupported"; + | "unsupported" + | "dyn-field-policy"; /** A type failure's structured payload: what the commit points of the * localized-failure design inspect (PoisonError.failure carries the same diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index f15e0dc86f..e25c689b51 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -2541,6 +2541,19 @@ export function collectClassShapeInner( if (initializerSymbol && (initializerSymbol.flags & ts.SymbolFlags.Enum) !== 0) continue; const type = lowerer.irTypeOf(member.name); if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); + if ( + type.kind === "dyn" && + !lowerer.liftDynFieldPolicy && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) && + (member.type !== undefined || + (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) + ) { + lowerer.dynFieldPolicyFence( + member.name, + `${dynFieldTypeSpelling(lowerer, member.name)}-typed static fields`, + ); + } staticFields.push({ name: member.name.text, type, @@ -3044,9 +3057,22 @@ export function collectClassShapeInner( if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type)) type = DYN; } if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); - // A field whose type maps to the checked dynamic (`any`, an - // option bag, a degraded type) stores it natively, as JavaScript - // classes and constructor-assigned fields do. + // Bundled JS declares fields without annotations before assigning + // them in the constructor. Use the same native checked-dynamic + // storage as implicit constructor-assigned JS fields. In + // TypeScript the refusal is a policy fence that a flush retry lifts + // (Lowerer.liftDynFieldPolicy). + if ( + type.kind === "dyn" && + !lowerer.liftDynFieldPolicy && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) + ) { + lowerer.dynFieldPolicyFence( + member.name, + `${dynFieldTypeSpelling(lowerer, member.name)}-typed class fields`, + ); + } const undefinedInitializer = type.kind === "dyn" && !member.initializer ? locOf(member) : undefined; if (fields.has(member.name.text)) { @@ -3177,6 +3203,17 @@ export function collectClassShapeInner( const shape = lowerer.paramShape(p); const type = shape.bodyType ?? shape.type; if (type.kind === "void") lowerer.badType(p.name, lowerer.typeOf(p.name)); + // The class-field dyn rule verbatim (KEEP NARROW). + if ( + type.kind === "dyn" && + !lowerer.liftDynFieldPolicy && + !lowerer.classFieldStoresDyn(lowerer.typeOf(p)) + ) { + lowerer.dynFieldPolicyFence( + p.name, + `${dynFieldTypeSpelling(lowerer, p)}-typed class fields`, + ); + } // `override x` (and any same-named inherited member) would // redeclare a base slot — the declared-field rule verbatim. if (fields.has(name)) { @@ -8919,6 +8956,14 @@ export function throwingSetterFn(lowerer: Lowerer, info: ClassInfo, prop: string }; } +/** The declared type a refused checked-dynamic class field names in its + * diagnostic (`'any'`, `'WeakMap'`): `unknown` and + * `object` fields store checked-dynamic values, so the refusal is about + * whatever else the declaration spells. */ +function dynFieldTypeSpelling(lowerer: Lowerer, node: ts.Node): string { + return `'${lowerer.checker.typeToString(lowerer.typeOf(node))}'`; +} + /** The base a constructor chain actually CALLS into: generic FAMILIES are * never constructed (no `%.constructor` exists), so an * instantiation's construction-relevant base is the family's own base — diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 3cce518173..877d6da633 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -764,15 +764,16 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // Localized failure's statement commit point (design §3.3): a // degradable type failure at a local, loop variable or module // global degrades that type and the statement lowers again. - const slot = - attempt < MAX_DEGRADE_RETRIES && !deferFences - ? lowerer.degradeAtCommit(e, STATEMENT_SLOTS) - : null; - if (slot === null) throw e; + // A declaration this statement flushed inside a probe (where + // no retry runs) can retry here too. + if (attempt >= MAX_DEGRADE_RETRIES || deferFences) throw e; + const slot = lowerer.degradeAtCommit(e, STATEMENT_SLOTS); + if (slot === null && !lowerer.reopenFlushedAtCommit(diagsBefore)) throw e; // A failed attempt adds nothing to `out` but the TDZ boxes // forward captures declared (they stay declared), so the // retry appends after them. - pendingSlots.push(slot); + lowerer.dropAttemptDiags(diagsBefore); + if (slot !== null) pendingSlots.push(slot); } } for (const slot of pendingSlots) lowerer.noteDynamicSlot(slot); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 00b62dcc41..e578e741fc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -988,6 +988,9 @@ export interface LowererMode { * remainder maps them alike, so the declarations reachable emit * registered with dynamic slots collect there too). */ degradedTypes?: ReadonlyMap; + /** Localized failure: the declarations reachable emit registered with + * dynamic slots (the coverage remainder registers them too). */ + retriedSymbols?: ReadonlySet; /** LowerOptions.externalTypes, threaded through every lowering pass. */ externalTypes?: ReadonlyMap; /** LowerOptions.externalTypeSpecifiersByFile, shared by every pass. */ @@ -1396,6 +1399,7 @@ export function lowerToIr( remainder: true, alreadyFlushed: resultLowerer.flushedSymbols, degradedTypes: resultLowerer.degradedTypes, + retriedSymbols: resultLowerer.retriedSymbols, inlineRequireCycleCalls, targetPlatform, ffiImports, @@ -3779,6 +3783,10 @@ export class Lowerer { readonly degradedTypes = new Map(); /** The dynamic slots the commit points created: one SC6104 each. */ readonly dynamicSlots: DynamicSlot[] = []; + /** Declarations retryDeferred registered. */ + readonly retriedSymbols = new Set(); + /** LowererMode.retriedSymbols. */ + readonly priorRetriedSymbols: ReadonlySet; /** Bodies run() skipped because their declaration had not registered. */ readonly skippedBodies = new Set(); /** Declarations retryDeferred registered, in order: run() lowers the @@ -3790,6 +3798,9 @@ export class Lowerer { ts.Symbol, { symbolOf: () => ts.Symbol | undefined; collect: () => void } >(); + /** True while a flush retry collects a declaration: the dyn class + * field policy (dynFieldPolicyFence) is lifted for reached classes. */ + liftDynFieldPolicy = false; /** Collection diagnostics that a flush retry may simply run again (a * derived class whose base had not registered yet). */ readonly retriableDiags = new WeakSet(); @@ -3878,6 +3889,7 @@ export class Lowerer { // The mode's options, sorted by name; the values derived from them follow. this.alreadyFlushed = mode.alreadyFlushed ?? new Set(); for (const [id, degraded] of mode.degradedTypes ?? []) this.degradedTypes.set(id, degraded); + this.priorRetriedSymbols = mode.retriedSymbols ?? new Set(); this.externalTypes = mode.externalTypes ?? new Map(); this.ffiBindingSymbols = mode.ffiBindingSymbols ?? null; this.ffiImports = mode.ffiImports ?? []; @@ -4949,7 +4961,14 @@ export class Lowerer { } collectProgram(parts: FileParts[]): void { - return collectProgram(this, parts); + collectProgram(this, parts); + // The coverage remainder registers the declarations reachable emit + // registered with dynamic slots (retryDeferred), so it neither reports + // them as unreached nor blocks their uses. + for (const symbol of this.priorRetriedSymbols) { + const diags = this.deferredDiags.get(symbol); + if (diags !== undefined) this.retryDeferred(symbol, diags); + } } /** Names every file's %init and registers the run-once guard globals @@ -9346,6 +9365,9 @@ export class Lowerer { this.diagSink.push(d); return; } + // A probe's copy of a declaration's flushed diagnostics, committed + // after a flush retry registered the declaration: nothing to report. + if (pending && this.retriedSymbols.has(pending.symbol)) return; if (pending) { this.commitFlush(pending.symbol, pending.source); const group = this.rejectedGroups.get(pending.symbol); @@ -9434,7 +9456,7 @@ export class Lowerer { const failure = e.failure; const type = e.failedType; if (failure === undefined || type === undefined) return null; - if (!failure.degradable) return null; + if (!failure.degradable || failure.family === "dyn-field-policy") return null; if (!slots.has(failure.slot)) return null; const id = (type as { id?: number }).id; // Already degraded and still failing: no progress, commit as today. @@ -9467,6 +9489,51 @@ export class Lowerer { }); } + /** Localized failure, statement commit point for declarations a probe + * flushed: a statement committing the flushed diagnostics of a deferred + * declaration (inside a capture buffer the flush could not retry) reopens + * it here, outside every probe. When the declaration's diagnostics are + * all retriable, they leave the build, the declaration retries + * (retryDeferred), and on success the caller lowers the statement again. + * Returns whether some declaration registered. */ + reopenFlushedAtCommit(diagsBefore: number): boolean { + if (this.tierMode !== "auto" || this.diagSink !== null) return false; + const groups = new Set(); + for (let i = diagsBefore; i < this.diags.length; i++) { + const group = this.diags[i]!.rejects; + if (group !== undefined && !group.startsWith("statement @")) groups.add(group); + } + if (groups.size === 0) return false; + let registered = false; + for (const [symbol, group] of [...this.rejectedGroups]) { + if (!groups.has(group) || !this.deferredCollectors.has(symbol)) continue; + const committed = this.diags.filter((d) => d.rejects === group); + this.diags.splice(0, this.diags.length, ...this.diags.filter((d) => d.rejects !== group)); + this.flushedSymbols.delete(symbol); + this.rejectedGroups.delete(symbol); + if (this.retryDeferred(symbol, committed)) { + registered = true; + continue; + } + // Not retriable: the declaration's diagnostics commit as before. + const last = this.deferredDiags.get(symbol) ?? committed; + this.deferredDiags.delete(symbol); + this.flushedSymbols.add(symbol); + this.noteRejected(symbol, last); + for (const d of last) this.pushDiag(d); + } + return registered; + } + + /** A failed attempt's own diagnostics leave the build before its unit + * lowers again; flushed declaration diagnostics (`rejects`) stay, since + * the retry will not flush them a second time. */ + dropAttemptDiags(diagsBefore: number): void { + const kept = this.diags.slice(diagsBefore).filter((d) => d.rejects !== undefined); + this.diags.length = diagsBefore; + this.diags.push(...kept); + } + /** Records a dynamic slot whose unit lowered with it (one per site). */ noteDynamicSlot(slot: DynamicSlot): void { const seen = this.dynamicSlots.some( @@ -9478,6 +9545,22 @@ export class Lowerer { if (!seen) this.dynamicSlots.push(slot); } + /** The SC1090 refusal of checked-dynamic class fields in TypeScript + * (classFieldStoresDyn): a policy fence, not a missing representation. + * A flush retry lifts it (liftDynFieldPolicy), so a reached class with + * an 'any' field registers. */ + dynFieldPolicyFence(node: ts.Node, feature: string): never { + const diag = unsupportedDiag("SC1090", locOf(node), feature); + const failure = typeFailureOf( + node, + this.checker.typeToString(this.typeOf(node)), + "dyn-field-policy", + ); + diag.typeFailure = failure; + this.pushDiag(diag); + throw new PoisonError(failure, this.typeOf(node)); + } + badType(node: ts.Node, type: ts.Type): never { const widened = this.checker.getBaseTypeOfLiteralType(type); // Types declared by the ADOPTED @types/node (Buffer, NodeJS.Timeout, @@ -12454,6 +12537,12 @@ export class Lowerer { continue; } const failure = diag.typeFailure; + // The dyn class field policy lifts during the retry; nothing to + // degrade. + if (failure?.family === "dyn-field-policy") { + retriable = true; + continue; + } const type = failure === undefined ? undefined : this.failedTypes.get(failure); const id = (type as { id?: number } | undefined)?.id; if ( @@ -12474,7 +12563,16 @@ export class Lowerer { slots.push({ loc: diag.loc, failure, code: diag.code, reason: diag.message }); } this.deferredDiags.delete(symbol); - if (this.collectDeferring(collector.symbolOf, collector.collect) === null) { + const lifted = this.liftDynFieldPolicy; + this.liftDynFieldPolicy = true; + let registered: boolean; + try { + registered = this.collectDeferring(collector.symbolOf, collector.collect) === null; + } finally { + this.liftDynFieldPolicy = lifted; + } + if (registered) { + this.retriedSymbols.add(symbol); this.deferredCollectors.delete(symbol); for (const [name, deferred] of this.deferredClassByName) if (deferred === symbol) this.deferredClassByName.delete(name); diff --git a/packages/compiler/src/frontend/lowering/type-failure.ts b/packages/compiler/src/frontend/lowering/type-failure.ts index 1267779735..3502a9bbf7 100644 --- a/packages/compiler/src/frontend/lowering/type-failure.ts +++ b/packages/compiler/src/frontend/lowering/type-failure.ts @@ -34,6 +34,7 @@ const DEGRADABLE_FAMILIES: ReadonlySet = new Set Date: Sun, 25 Oct 2026 17:36:08 -0700 Subject: [PATCH 13/49] Degrade parameters and returns of function expressions and generic instances where they commit A function expression's or arrow's parameter and return types commit at the statement that holds it, and a generic instance's at the instance drain. Both are now localized-failure commit points for declaration slots: the failing type degrades and the statement or instance lowers again. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stmts.ts | 8 ++- .../compiler/src/frontend/lowering/lowerer.ts | 52 +++++++++++++------ .../localized-failure-function-slots.ts | 28 ++++++++++ .../localized-failure-function-slots.ts.json | 1 + 4 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 tests/corpus/localized-failure-function-slots.ts create mode 100644 tests/harness/corpus-costs/localized-failure-function-slots.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 877d6da633..72fec97e78 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -854,11 +854,17 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir * at most this many times (each retry degrades one more type). */ const MAX_DEGRADE_RETRIES = 16; -/** The slot kinds lowerStmts' commit point degrades (stage L1). */ +/** The slot kinds lowerStmts' commit point degrades: locals, loop + * variables and module globals (stage L1), and the parameters and returns + * of the function expressions, arrows and methods a statement holds + * (stage L2). */ const STATEMENT_SLOTS: ReadonlySet = new Set([ "local", "global", "loop", + "param", + "return", + "field", ]); /** One attempt at lowering a statement of lowerStmts into `out`. */ diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e578e741fc..de84fa3a9f 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -8301,13 +8301,10 @@ export class Lowerer { // generic method's this/super fence, a fenced parameter // default): the diagnostic is recorded — the instance skips // like a signature-blocked function (lowerFunction's rule). - try { - functions.push( - this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)), - ); - } catch (e) { - if (!(e instanceof PoisonError)) throw e; - } + const fn = this.lowerUnitDegrading(() => + this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)), + ); + if (fn !== null) functions.push(fn); } // Emit-override specializations queued by the emit sites above (a // body can queue more — the super-forward chain — and generic @@ -8983,17 +8980,16 @@ export class Lowerer { this.genericDemandPriority.set(inst, ref); // Body-level poisons skip the instance after retaining the // diagnostic and every edge fired before poisoning. - try { - instanceFunctions.push( - this.withGenericDemandOwner(owner, () => - this.shapes.withDeclaredOrderPriority(ref, () => - this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)), - ), + const fn = this.lowerUnitDegrading(() => + this.withGenericDemandOwner(owner, () => + this.shapes.withDeclaredOrderPriority(ref, () => + this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)), ), - ); + ), + ); + if (fn !== null) { + instanceFunctions.push(fn); relieveHeapPressure(); - } catch (e) { - if (!(e instanceof PoisonError)) throw e; } } } @@ -9534,6 +9530,30 @@ export class Lowerer { this.diags.push(...kept); } + /** Lowers one unit whose body-level poison skips it (a generic + * instance), with localized failure's commit point: a degradable type + * failure at one of its declaration slots degrades the type and the unit + * lowers again (at most MAX_DECLARATION_RETRIES times). Null when it + * still poisons; its diagnostics are then committed as before. */ + lowerUnitDegrading(lower: () => T): T | null { + const slots: DynamicSlot[] = []; + for (let attempt = 0; ; attempt++) { + const mark = this.diags.length; + try { + const result = lower(); + for (const slot of slots) this.noteDynamicSlot(slot); + return result; + } catch (e) { + if (!(e instanceof PoisonError)) throw e; + if (attempt >= MAX_DECLARATION_RETRIES) return null; + const slot = this.degradeAtCommit(e, DECLARATION_SLOTS); + if (slot === null) return null; + slots.push(slot); + this.dropAttemptDiags(mark); + } + } + } + /** Records a dynamic slot whose unit lowered with it (one per site). */ noteDynamicSlot(slot: DynamicSlot): void { const seen = this.dynamicSlots.some( diff --git a/tests/corpus/localized-failure-function-slots.ts b/tests/corpus/localized-failure-function-slots.ts new file mode 100644 index 0000000000..0a7fdf641f --- /dev/null +++ b/tests/corpus/localized-failure-function-slots.ts @@ -0,0 +1,28 @@ +// Localized failure, stage L2: parameters and returns of arrows, function +// expressions and generic instances become checked dynamic slots. +type Hook = Map string> | AbortSignal | (() => number) | Set; + +interface Model { + id: T; + hook: Hook; +} + +function retry(label: string, request: () => Promise | Hook): T | string { + const r = request(); + return typeof r === "function" ? label + ":" + String(r()) : label + ":other"; +} + +function describe(model: Model): string { + return `${String(model.id)}/${model.hook instanceof Set ? model.hook.size : typeof model.hook}`; +} + +const run = (hook: Hook, n: number): string => (typeof hook === "function" ? String(hook() + n) : "set"); +const make = function (size: number): Hook { + return new Set(Array.from({ length: size }, (_, i) => `k${i}`)); +}; + +console.log(run(() => 40, 2), run(new Set(["a"]), 1)); +const h = make(3); +console.log(h instanceof Set ? h.size : -1); +console.log(retry("a", () => () => 7), retry("b", () => new Set())); +console.log(describe({ id: 5, hook: new Set(["x", "y"]) }), describe({ id: "s", hook: () => 1 })); diff --git a/tests/harness/corpus-costs/localized-failure-function-slots.ts.json b/tests/harness/corpus-costs/localized-failure-function-slots.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-function-slots.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 624d2bf8734c5e422d180c211c0e4328a2ba38a0 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 17:54:37 -0700 Subject: [PATCH 14/49] Record order-parity baselines for the stage L2 localized-failure corpus programs Co-Authored-By: Claude Opus 5.5 --- .../tests/corpus/localized-failure-cycles.ts.json | 6 ++++++ .../tests/corpus/localized-failure-declarations.ts.json | 6 ++++++ .../tests/corpus/localized-failure-function-slots.ts.json | 6 ++++++ .../tests/corpus/localized-failure-pi-shapes.ts.json | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-cycles.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-declarations.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-function-slots.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-pi-shapes.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-cycles.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-cycles.ts.json new file mode 100644 index 0000000000..7df4fd38f7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-cycles.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-cycles.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-declarations.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-declarations.ts.json new file mode 100644 index 0000000000..0530e8e135 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-declarations.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-declarations.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-function-slots.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-function-slots.ts.json new file mode 100644 index 0000000000..88890074df --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-function-slots.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-function-slots.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-pi-shapes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-pi-shapes.ts.json new file mode 100644 index 0000000000..d1603f0299 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-pi-shapes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-pi-shapes.ts" + ], + "diags": [] +} From 0b483e8650dc6a87b565a7ed9fbcbb7578ef4f55 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:04:29 -0500 Subject: [PATCH 15/49] Map UTF-16 indexes of long non-ASCII strings in constant time and quick-check normalize - Index walks (slice, substring, substr, startsWith/endsWith with a position, lastIndexOf results) on a non-ASCII receiver of 64 KiB or more: once they would have paid for one pass over it, the receiver's index entry keeps every unit's byte offset (a 32-bit base per 64 units and one byte per unit), and indexes map with two loads. Before that, a walk within 64 units of the hot cursor starts from it, without the index search. The index entry stays 128 bytes on 64-bit targets. - lastIndexOf on non-ASCII receivers tests the needle's first byte before comparing, as the ASCII path does. - normalize: a quick check (UAX #15) returns the receiver when every code point is inert for the form: unchanged when normalized alone, combining class 0, and, in the ranges checked, never the second element of a composition. Other text normalizes segment by segment, each starting at a starter, so runs of inert code points copy as they are and a lone precomposed letter's result is kept per form. The per-code-point facts come from unicode_normalize, 256 code points at a time. --- .../corpus/8008-string-index-paths.ts.json | 6 + packages/runtime/src/scr_regex.c | 197 +++++++++++++++++- packages/runtime/src/scr_string.c | 189 ++++++++++++----- tests/corpus/8008-string-index-paths.ts | 102 +++++++++ .../8008-string-index-paths.ts.json | 1 + 5 files changed, 435 insertions(+), 60 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8008-string-index-paths.ts.json create mode 100644 tests/corpus/8008-string-index-paths.ts create mode 100644 tests/harness/corpus-costs/8008-string-index-paths.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8008-string-index-paths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8008-string-index-paths.ts.json new file mode 100644 index 0000000000..5e2082ab9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8008-string-index-paths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8008-string-index-paths.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index 37f94bd656..4296ce791e 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -2469,6 +2469,146 @@ static int scr_unicode_normalize(uint32_t **pdst, const uint32_t *src, int len, return (int)count; } +/* Normalization quick check (UAX #15): a string whose every code point is + * inert for the form is already in that form. A code point is inert when + * normalizing it alone leaves it unchanged, its canonical combining class + * is 0, and (for the composing forms) no composition can take it as its + * second element. The last condition holds in the ranges below: none holds + * a code point with combining class 0 that composes with a preceding one + * (those are Hangul jamo vowels and trailing consonants, U+3099/U+309A, + * and vowel signs of Indic and Southeast Asian scripts above U+0900), so a + * code point outside them takes the full algorithm. Inert flags are + * computed through unicode_normalize, 256 code points at a time, when a + * block is first seen. */ +#define SCR_NQC_BLOCKS 0x200 +typedef struct { + uint8_t known[4][SCR_NQC_BLOCKS]; + uint8_t inert[4][SCR_NQC_BLOCKS][32]; + uint8_t starter[4][SCR_NQC_BLOCKS][32]; /* class 0, never a second element */ + struct { + uint32_t cp; + uint8_t len; + char bytes[15]; + } single[4][256]; /* a lone code point's normalized UTF-8 */ +} ScrNormalizeQuick; +static SCR_TL ScrNormalizeQuick *scr_nqc; + +static void scr_nqc_free(void) { + free(scr_nqc); + scr_nqc = NULL; +} + +static bool scr_nqc_in_ranges(uint32_t c) { + return c < 0x900 || (c >= 0x2000 && c < 0x3099) || (c >= 0x309B && c < 0xA000) || + (c >= 0xAC00 && c < 0xD7A4) || (c >= 0xF900 && c < 0x10000) || + (c >= 0x1F000 && c < 0x1FB00); +} + +/* Bit 0: inert. Bit 1: a starter, with class 0 and never the second element + * of a composition, so nothing before it reorders or composes with it. */ +/* Whether c has class 0 and lies in the ranges above. U+0345 is the one + * code point of class 240, which the reordering test cannot tell from + * class 0; a mark of class 1..239 after U+0345 moves before it. */ +static bool scr_nqc_class0(uint32_t c) { + if (c == 0x345 || !scr_nqc_in_ranges(c)) return false; + uint32_t pair[2] = {0x345, c}; + uint32_t *out = NULL; + int n = unicode_normalize(&out, pair, 2, UNICODE_NFD, NULL, scr_normalize_realloc); + bool starter = n >= 2 && out[0] == 0x345; + free(out); + return starter; +} + +static unsigned scr_nqc_compute(uint32_t c, UnicodeNormalizationEnum mode) { + if (!scr_nqc_class0(c)) return 0; + uint32_t *out = NULL; + int n = unicode_normalize(&out, &c, 1, mode, NULL, scr_normalize_realloc); + bool same = n == 1 && out[0] == c; + free(out); + if (same) return 3u; + /* A starter's decomposition must begin with one too (U+FF9E's + * compatibility decomposition is U+3099, which composes with the code + * point before it). */ + UnicodeNormalizationEnum decompose = + mode == UNICODE_NFKC || mode == UNICODE_NFKD ? UNICODE_NFKD : UNICODE_NFD; + out = NULL; + n = unicode_normalize(&out, &c, 1, decompose, NULL, scr_normalize_realloc); + bool leads = n >= 1 && scr_nqc_class0(out[0]); + free(out); + return leads ? 2u : 0u; +} + +/* scr_nqc_compute's bits for c, from its block's flags. */ +static unsigned scr_nqc_class(uint32_t c, UnicodeNormalizationEnum mode) { + if (c >= (uint32_t)SCR_NQC_BLOCKS << 8) return 0; + if (!scr_nqc) { + scr_nqc = calloc(1, sizeof *scr_nqc); + if (!scr_nqc) return 0; + scr_atexit(scr_nqc_free); + } + uint32_t block = c >> 8; + uint8_t *inert = scr_nqc->inert[mode][block]; + uint8_t *starter = scr_nqc->starter[mode][block]; + if (!scr_nqc->known[mode][block]) { + for (uint32_t i = 0; i < 256; i++) { + unsigned bits = scr_nqc_compute((block << 8) | i, mode); + if (bits & 1) inert[i >> 3] |= (uint8_t)(1u << (i & 7)); + if (bits & 2) starter[i >> 3] |= (uint8_t)(1u << (i & 7)); + } + scr_nqc->known[mode][block] = 1; + } + unsigned bit = c & 0xff; + return ((inert[bit >> 3] >> (bit & 7)) & 1) | (((starter[bit >> 3] >> (bit & 7)) & 1) << 1); +} + +/* Normalize the code points of data[from, to) and append them. */ +static void scr_nqc_segment(ScrJsonBuf *b, const char *data, size_t from, size_t to, + UnicodeNormalizationEnum mode) { + size_t first_step; + uint32_t first = scr_case_decode(data + from, &first_step); + if (first_step == to - from && scr_nqc) { + /* One code point (a precomposed letter between inert ones): its + * normalized bytes repeat, so they are kept per form. */ + __typeof__(scr_nqc->single[0][0]) *slot = &scr_nqc->single[mode][first & 0xff]; + if (slot->cp != first || slot->len == 0) { + uint32_t *output = NULL; + int count = unicode_normalize(&output, &first, 1, mode, NULL, scr_normalize_realloc); + if (count < 0) scr_regex_oom(); + ScrJsonBuf one; + scr_jb_init(&one); + for (int i = 0; i < count; i++) scr_case_put_cp(&one, output[i]); + free(output); + ScrStr *bytes = scr_jb_finish(&one); + if (bytes->len == 0 || bytes->len > sizeof slot->bytes) { + scr_jb_write(b, bytes->data, bytes->len); + scr_str_release(bytes); + return; + } + slot->cp = first; + slot->len = (uint8_t)bytes->len; + memcpy(slot->bytes, bytes->data, bytes->len); + scr_str_release(bytes); + } + scr_jb_write(b, slot->bytes, slot->len); + return; + } + uint32_t local[32]; + uint32_t *input = to - from <= 32 ? local : malloc((to - from) * sizeof *input); + if (!input) scr_regex_oom(); + int length = 0; + for (size_t offset = from; offset < to;) { + size_t step; + input[length++] = scr_case_decode(data + offset, &step); + offset += step; + } + uint32_t *output = NULL; + int count = scr_unicode_normalize(&output, input, length, mode); + if (input != local) free(input); + if (count < 0) scr_regex_oom(); + for (int i = 0; i < count; i++) scr_case_put_cp(b, output[i]); + free(output); +} + ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { UnicodeNormalizationEnum mode; if (form->len == 3 && !memcmp(form->data, "NFC", 3)) mode = UNICODE_NFC; @@ -2483,23 +2623,58 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { /* Every ASCII string is already normalized in all four forms. Validate * the form first: even an empty subject must reject an invalid form. */ if (scr_text_ascii(s)) return scr_str_retain((ScrStr *)s); - if (s->len > INT32_MAX / sizeof(uint32_t)) scr_regex_oom(); - uint32_t *input = malloc((s->len + 1) * sizeof(uint32_t)); - if (!input) scr_regex_oom(); - int length = 0; + bool inert = true; for (size_t offset = 0; offset < s->len;) { + if ((unsigned char)s->data[offset] < 0x80) { + offset++; + continue; + } size_t step; - input[length++] = scr_case_decode(s->data + offset, &step); + uint32_t c = scr_case_decode(s->data + offset, &step); offset += step; + if (!(scr_nqc_class(c, mode) & 1)) { + inert = false; + break; + } } - uint32_t *output = NULL; - int count = scr_unicode_normalize(&output, input, length, mode); - free(input); - if (count < 0) scr_regex_oom(); + if (inert) return scr_str_retain((ScrStr *)s); + if (s->len > INT32_MAX / sizeof(uint32_t)) scr_regex_oom(); + /* An inert code point bounds reordering and composition, and so does any + * other starter (class 0, never a composition's second element), so the + * text normalizes segment by segment. A segment starts at a starter: runs + * of inert code points copy as they are, and a segment with any other + * code point goes through scr_unicode_normalize. */ ScrJsonBuf buffer; scr_jb_init(&buffer); - for (int i = 0; i < count; i++) scr_case_put_cp(&buffer, output[i]); - free(output); + size_t seg = 0, last_inert = 0; + bool plain = true; + for (size_t offset = 0; offset < s->len;) { + unsigned char lead = (unsigned char)s->data[offset]; + size_t step = 1; + unsigned kind = 3; + if (lead >= 0x80) kind = scr_nqc_class(scr_case_decode(s->data + offset, &step), mode); + if (kind & 1) { + if (!plain) { + scr_nqc_segment(&buffer, s->data, seg, offset, mode); + seg = offset; + plain = true; + } + last_inert = offset; + } else if (kind & 2) { + if (plain) scr_jb_write(&buffer, s->data + seg, offset - seg); + else scr_nqc_segment(&buffer, s->data, seg, offset, mode); + seg = offset; + plain = false; + } else if (plain) { + size_t start = last_inert > seg ? last_inert : seg; + scr_jb_write(&buffer, s->data + seg, start - seg); + seg = start; + plain = false; + } + offset += step; + } + if (plain) scr_jb_write(&buffer, s->data + seg, s->len - seg); + else scr_nqc_segment(&buffer, s->data, seg, s->len, mode); return scr_jb_finish(&buffer); } diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 64a4772e5c..71d6299418 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -83,6 +83,12 @@ typedef struct { size_t cu; /* UTF-16 code-unit offset, always a code-point boundary */ size_t cb; /* matching UTF-8 byte offset, never a continuation byte */ } ScrSidxPoint; +/* One allocation: the 32-bit base of every 64th unit follows the deltas. */ +typedef struct { + size_t n; /* the UTF-16 length the offsets describe */ + uint32_t *base; /* byte offset of every 64th unit */ + uint8_t delta[]; /* each unit's byte offset past its block's base */ +} ScrSidxOffsets; typedef struct ScrSidx { const ScrStr *s; /* NULL = empty slot */ size_t u16len; /* SCR_U16_UNKNOWN until the whole current string */ @@ -94,10 +100,12 @@ typedef struct ScrSidx { size_t indexed_cb; bool no_more_points; /* metadata allocation failed/overflowed: fail open */ bool points_complete; /* points cover every stride of indexed prefix */ + uint32_t walks; /* index walks before the offsets exist */ uint16_t *units; /* the UTF-16 code units, once reads warrant them */ - size_t nunits; - size_t reads; /* indexed reads that missed the unit array */ -} ScrSidx; + uint32_t nunits; /* units and offsets need at most 1 << 24 bytes */ + uint32_t reads; /* indexed reads that missed the unit array */ + ScrSidxOffsets *offsets; /* every unit's byte offset, once walks warrant */ +} ScrSidx; /* 128 bytes on 64-bit targets: every lookup clears or copies one */ /* Keep short-string cursor traffic out of the sparse cache. A short-lived * one-byte receiver can be far more common than a large indexed one; sharing * the round-robin slots would otherwise rebuild a warm index every few calls. @@ -136,6 +144,7 @@ static void scr_sidx_clear(ScrSidx *e, unsigned *live) { scr_str_scan.n = 0; } free(e->units); + if (e->offsets) free(e->offsets); /* rare: skip the call on the hot clear */ memset(e, 0, sizeof(*e)); } @@ -337,6 +346,9 @@ static void scr_sidx_concat_append(const ScrStr *s, size_t oldlen) { e->units = NULL; e->nunits = 0; e->reads = 0; + free(e->offsets); /* and so do the offsets */ + e->offsets = NULL; + e->walks = 0; } } } @@ -1576,6 +1588,8 @@ static void scr_sidx_ascii_run(const ScrStr *s, ScrSidx *e) { /* How far past its end a forward access may grow the cached ASCII run, and * how far beyond the access the growth looks ahead. */ #define SCR_SIDX_ASCII_REACH ((size_t)4096) +/* How far from the hot cursor an index walks from it. */ +#define SCR_SIDX_NEAR_UNITS ((size_t)64) #define SCR_SIDX_ASCII_AHEAD ((size_t)256) /* Grow the cached ASCII run forward until it covers byte `want` (plus a @@ -1624,8 +1638,118 @@ static inline size_t scr_u16_to_byte_c(const ScrStr *s, ScrSidx *e, size_t u16, return scr_u16_to_byte_walk(s, e, u16, mid); } +/* Index walks on one long non-ASCII receiver (slices, substrings and + * indexed reads taken across it) each step through bytes from the nearest + * known position. Once those walks would have paid for one pass over the + * receiver (a walk per 32 bytes, at least 64 walks), a receiver from + * SCR_SIDX_MIN_BYTES to SCR_SIDX_OFFSETS_MAX bytes keeps every unit's byte + * offset: a 32-bit base per 64 units and one byte per unit past it (64 units + * span at most 192 bytes). The low half of an astral character has its high + * half's offset. The offsets are freed with the entry, and an append drops + * them (scr_sidx_concat_append). */ +#define SCR_SIDX_OFFSET_BLOCK ((size_t)64) +#define SCR_SIDX_OFFSETS_MAX ((size_t)1 << 24) +static bool scr_sidx_offsets(const ScrStr *s, ScrSidx *e) { + if (e->offsets) return true; + if (s->len < SCR_SIDX_MIN_BYTES || s->len > SCR_SIDX_OFFSETS_MAX) return false; + if (++e->walks < 64 || (size_t)e->walks * 32 < s->len) return false; + size_t n = scr_sidx_len(s, e); + if (s->ascii || e->u16len == s->len) return false; + size_t ndelta = (n + 1 + 3) & ~(size_t)3; + ScrSidxOffsets *o = + malloc(sizeof *o + ndelta + (n / SCR_SIDX_OFFSET_BLOCK + 1) * sizeof(uint32_t)); + if (!o) { + e->walks = 0; + return false; + } + o->n = n; + o->base = (uint32_t *)(o->delta + ndelta); + uint32_t *base = o->base; + uint8_t *delta = o->delta; + size_t cu = 0, cb = 0; + while (cb < s->len && cu < n) { + size_t seq = scr_utf8_seq_len((unsigned char)s->data[cb]); + for (size_t w = seq == 4 ? 2 : 1; w > 0 && cu < n; w--, cu++) { + if (cu % SCR_SIDX_OFFSET_BLOCK == 0) base[cu / SCR_SIDX_OFFSET_BLOCK] = (uint32_t)cb; + delta[cu] = (uint8_t)(cb - base[cu / SCR_SIDX_OFFSET_BLOCK]); + } + cb += seq; + } + e->offsets = o; + return true; +} + +static inline size_t scr_sidx_offset_at(const ScrSidx *e, size_t u16) { + return e->offsets->base[u16 / SCR_SIDX_OFFSET_BLOCK] + e->offsets->delta[u16]; +} + +/* Walk from a known code-point boundary (cu, cb) to UTF-16 index u16 and + * leave the hot cursor there. Same contract as scr_u16_to_byte_walk. */ +static size_t scr_sidx_walk_from(const ScrStr *s, ScrSidx *e, size_t cu, size_t cb, size_t u16, + bool *mid, bool refresh_run) { + while (cu > u16) { + /* Eight bytes starting inside a sequence can reach back at most three + * more bytes to its lead. Such a span contains at most nine UTF-16 + * units (seven ASCII bytes plus an astral pair), so it cannot overshoot + * a target at least nine units away. */ + if (cu - u16 >= 9 && cb >= 8) { + size_t start = cb - 8; + while (start > 0 && ((unsigned char)s->data[start] & 0xC0) == 0x80) + start--; + cu -= scr_utf16_units_span(s->data + start, cb - start, NULL); + cb = start; + } else { + scr_sidx_back(s, &cu, &cb); + } + } + bool m = false; + while (cu < u16 && cb < s->len) { + if (u16 - cu >= 9 && s->len - cb >= 8) { + size_t end = cb + 8; + while (end < s->len && ((unsigned char)s->data[end] & 0xC0) == 0x80) + end++; + cu += scr_utf16_units_span(s->data + cb, end - cb, NULL); + cb = end; + continue; + } + SCR_SIDX_STEP(); + size_t seq = scr_utf8_seq_len((unsigned char)s->data[cb]); + size_t w = seq == 4 ? 2 : 1; + if (cu + w > u16) { /* u16 lands between the halves of an astral char */ + m = true; + break; + } + cu += w; + cb += seq; + } + e->cu = cu; + e->cb = cb; + if (refresh_run && cb < s->len && (unsigned char)s->data[cb] < 0x80) + scr_sidx_ascii_run(s, e); + *mid = m; + return cb; +} + static __attribute__((noinline)) size_t scr_u16_to_byte_walk(const ScrStr *s, ScrSidx *e, size_t u16, bool *mid) { + if (scr_sidx_offsets(s, e)) { + *mid = false; + if (u16 >= e->offsets->n) return s->len; + size_t cb = scr_sidx_offset_at(e, u16); + size_t cu = u16; + if (u16 > 0 && scr_sidx_offset_at(e, u16 - 1) == cb) { + *mid = true; /* the low half of an astral character */ + cu = u16 - 1; + } + e->cu = cu; + e->cb = cb; + return cb; + } + /* Within a short distance of the hot cursor (a scanner + * taking slices near its position), walk from that cursor: no index + * search, extension or ASCII-run growth is cheaper. */ + if (e->cb <= s->len && scr_sidx_abs_diff(e->cu, u16) <= SCR_SIDX_NEAR_UNITS) + return scr_sidx_walk_from(s, e, e->cu, e->cb, u16, mid, false); /* A short step forward from the hot cursor (a sequential scan of * non-ASCII text) walks from the cursor itself. */ if (u16 >= e->cu && u16 - e->cu <= 8) { @@ -1665,48 +1789,7 @@ static __attribute__((noinline)) size_t scr_u16_to_byte_walk(const ScrStr *s, Sc } scr_sidx_materialize_identity_prefix(s, e); ScrSidxPoint near = scr_sidx_near_u16(s, e, u16); - size_t cu = near.cu, cb = near.cb; - while (cu > u16) { - /* Eight bytes starting inside a sequence can reach back at most three - * more bytes to its lead. Such a span contains at most nine UTF-16 - * units (seven ASCII bytes plus an astral pair), so it cannot overshoot - * a target at least nine units away. */ - if (cu - u16 >= 9 && cb >= 8) { - size_t start = cb - 8; - while (start > 0 && ((unsigned char)s->data[start] & 0xC0) == 0x80) - start--; - cu -= scr_utf16_units_span(s->data + start, cb - start, NULL); - cb = start; - } else { - scr_sidx_back(s, &cu, &cb); - } - } - bool m = false; - while (cu < u16 && cb < s->len) { - if (u16 - cu >= 9 && s->len - cb >= 8) { - size_t end = cb + 8; - while (end < s->len && ((unsigned char)s->data[end] & 0xC0) == 0x80) - end++; - cu += scr_utf16_units_span(s->data + cb, end - cb, NULL); - cb = end; - continue; - } - SCR_SIDX_STEP(); - size_t seq = scr_utf8_seq_len((unsigned char)s->data[cb]); - size_t w = seq == 4 ? 2 : 1; - if (cu + w > u16) { /* u16 lands between the halves of an astral char */ - m = true; - break; - } - cu += w; - cb += seq; - } - e->cu = cu; - e->cb = cb; - if (cb < s->len && (unsigned char)s->data[cb] < 0x80) - scr_sidx_ascii_run(s, e); - *mid = m; - return cb; + return scr_sidx_walk_from(s, e, near.cu, near.cb, u16, mid, true); } static size_t scr_byte_to_u16_walk(const ScrStr *s, ScrSidx *e, size_t byte_off); @@ -1875,10 +1958,17 @@ static uint16_t *scr_str_units(const ScrStr *s, size_t *n) { * megabytes, which scanners read in full. The array is freed with the * entry, and an append drops it (scr_sidx_concat_append). */ #define SCR_SIDX_UNITS_MAX ((size_t)1 << 24) +static void scr_sidx_take_units(const ScrStr *s, ScrSidx *e) { + size_t n = 0; + e->units = scr_str_units(s, &n); + e->nunits = (uint32_t)n; +} + static bool scr_sidx_units(const ScrStr *s, ScrSidx *e) { if (!e->units) { - if (++e->reads < 32 || e->reads * 16 < s->len || s->len > SCR_SIDX_UNITS_MAX) return false; - e->units = scr_str_units(s, &e->nunits); + if (s->len > SCR_SIDX_UNITS_MAX || ++e->reads < 32 || (size_t)e->reads * 16 < s->len) + return false; + scr_sidx_take_units(s, e); } /* Publish the scan's receiver for emitted reads (scr_runtime.h). */ if (scr_str_scan.s != s) { @@ -1905,7 +1995,7 @@ void scr_str_scan_units_for(ScrStr *s, double from, double to) { (void)scr_sidx_units(s, e); return; } - e->units = scr_str_units(s, &e->nunits); + scr_sidx_take_units(s, e); } if (scr_str_scan.s != s) { scr_str_scan.s = s; @@ -1980,8 +2070,9 @@ double scr_str_last_index_of_from(ScrStr *s, ScrStr *needle, double position) { size_t start_byte = scr_u16_to_byte_c(s, e, start16, &mid); size_t max_start = s->len - needle->len; if (start_byte < max_start) max_start = start_byte; + char first = needle->data[0]; for (size_t i = max_start + 1; i-- > 0;) { - if (memcmp(s->data + i, needle->data, needle->len) == 0) { + if (s->data[i] == first && memcmp(s->data + i, needle->data, needle->len) == 0) { return (double)scr_byte_to_u16_c(s, e, i); } } diff --git a/tests/corpus/8008-string-index-paths.ts b/tests/corpus/8008-string-index-paths.ts new file mode 100644 index 0000000000..a9a748ad86 --- /dev/null +++ b/tests/corpus/8008-string-index-paths.ts @@ -0,0 +1,102 @@ +// UTF-16 index operations on long non-ASCII strings: slices and substrings +// walking forward and backward, interleaved on two strings, at astral +// characters and their halves, with negative and out-of-range indexes; +// lastIndexOf, startsWith and endsWith with positions; and normalize on +// text already in each form, composed and decomposed text, Hangul, +// compatibility characters and combining marks. + +const unit = "Ünïcödé tëxt with ß and 日本語 and émoji 😀 mixed in, "; +const big = unit.repeat(3000); +const other = "ascii words then 中文 and 🎉🎉 and ü, ".repeat(4000); + +function digest(text: string): number { + let h = 0; + for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) >>> 0; + return h; +} + +let forward = 0; +for (let i = 0; i + 30 < big.length; i += 7) { + forward = (forward * 33 + digest(big.slice(i, i + 20)) + digest(big.substring(i + 3, i + 9))) >>> 0; +} +let backward = 0; +for (let i = big.length - 1; i > 30; i -= 11) { + backward = (backward * 33 + digest(big.slice(i - 25, i)) + big.substring(i, i - 5).length) >>> 0; +} +let interleaved = 0; +for (let i = 0; i + 40 < other.length && i + 40 < big.length; i += 13) { + interleaved = (interleaved * 33 + digest(big.slice(i, i + 9)) + digest(other.slice(i + 1, i + 40))) >>> 0; +} +console.log(big.length, forward, backward, interleaved); + +// Indexes at astral characters and their halves. +const emojiAt = big.indexOf("😀", 100000); +console.log( + emojiAt, + JSON.stringify(big.slice(emojiAt, emojiAt + 1)), + JSON.stringify(big.slice(emojiAt + 1, emojiAt + 3)), + JSON.stringify(big.substring(emojiAt - 1, emojiAt + 1)), + big.charCodeAt(emojiAt + 1), + JSON.stringify(big.at(emojiAt + 1)), + JSON.stringify(big.charAt(emojiAt)), +); +console.log( + JSON.stringify(big.slice(-5)), + JSON.stringify(big.slice(-30, -20)), + big.slice(big.length - 3, big.length + 10).length, + JSON.stringify(big.substring(-10, 4)), + big.slice(5, 2).length, + JSON.stringify(big.slice(NaN, 3)), +); +for (const at of [0, 1, 52, 99999, 150000, big.length - 1, big.length, -1, -2, -big.length]) { + console.log(at, JSON.stringify(big.at(at)), JSON.stringify(big.charAt(at)), big.charCodeAt(at)); +} + +// lastIndexOf, startsWith and endsWith with positions. +console.log( + big.lastIndexOf("日本"), + big.lastIndexOf("日本", 100000), + big.lastIndexOf("😀", 50), + big.lastIndexOf("absent"), + big.lastIndexOf("é", 0), + big.lastIndexOf("Ü"), + big.lastIndexOf(""), + big.lastIndexOf("", 10), +); +let starts = 0; +let ends = 0; +for (let i = 0; i < big.length; i += 17) { + if (big.startsWith("tëxt", i)) starts++; + if (big.endsWith("😀", i)) ends++; +} +console.log(starts, ends, big.startsWith("Ü"), big.endsWith("in, "), big.startsWith("ünï"), big.endsWith("in, ", big.length - 1)); + +// normalize. +const samples = [ + "plain ascii", + "précomposé café naïve", + "décomposé café", + "日本語のテキスト 한국어 문장", + "각 각", + "fullwidth カタカナ fi ① ℌ ²", + "ậ ậ ͅ", + "Ω Å Å Ω K", + "が が か゚", + "emoji 😀 and \ud800 lone", + unit, +]; +for (const sample of samples) { + const forms = ["NFC", "NFD", "NFKC", "NFKD"].map((form) => sample.normalize(form)); + console.log( + forms.map((text) => `${text.length}:${digest(text)}`).join(" "), + forms.map((text) => text === sample).join(","), + ); +} +const longNfc = big.normalize(); +const longNfd = big.normalize("NFD"); +console.log(longNfc === big, longNfc.length, longNfd.length, digest(longNfd.slice(0, 100)), longNfd.normalize("NFC") === big); +try { + "x".normalize("NFX"); +} catch (error) { + console.log((error as Error).name, (error as Error).message); +} diff --git a/tests/harness/corpus-costs/8008-string-index-paths.ts.json b/tests/harness/corpus-costs/8008-string-index-paths.ts.json new file mode 100644 index 0000000000..3a1b75cbc5 --- /dev/null +++ b/tests/harness/corpus-costs/8008-string-index-paths.ts.json @@ -0,0 +1 @@ +{ "plain": 4784, "san": 2792 } From ee001517e1e472120bf8ed387d9b5c1654501649 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:21:26 -0500 Subject: [PATCH 16/49] Update tsc-ts to 0.0.10 tsc-ts 0.0.10 serves its API on a Unix socket with `--api --listen ` on Linux and macOS, reads its default library files from disk with `--default-libraries-from-disk`, and is built with scriptc 0.4.5 and a profile collected with it. It depends on @scriptc/threads 0.4.5. The release-age exclusions admit tsc-ts 0.0.10, its platform packages and @scriptc/threads 0.4.5. --- packages/compiler/package.json | 2 +- pnpm-lock.yaml | 74 +++++++++++++++++----------------- pnpm-workspace.yaml | 18 ++++----- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 03af418a5d..d1c39d8aca 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", - "tsc-ts": "0.0.9", + "tsc-ts": "0.0.10", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fc32620ad..f4a645778e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: specifier: workspace:* version: link:../runtime tsc-ts: - specifier: 0.0.9 - version: 0.0.9 + specifier: 0.0.10 + version: 0.0.10 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1360,8 +1360,8 @@ packages: cpu: [x64] os: [win32] - '@scriptc/threads@0.4.2': - resolution: {integrity: sha512-nR9FUxpOpgtTmJu6dejttElBL+JJTAZzJ2DZM+eNoujmLx9VpdlAVdgj3cu/MDsTUg/1sIUBq1ME1csC/Aa6yg==} + '@scriptc/threads@0.4.5': + resolution: {integrity: sha512-wHYrCItM68xYAwjnvM6VrRLDWd9+lAEQNjKh6PyLA8RdE7D8ts/VMoTFLmXRu2uvRywJuY18WPSCoEFRf3qfiA==} engines: {node: '>=24.0.0'} '@sinclair/typebox@0.25.24': @@ -1377,42 +1377,42 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} - '@tsc-ts/darwin-arm64@0.0.9': - resolution: {integrity: sha512-aIjSulef3DIEX7F3RRWLXyHWFgjXiVffBkrfE83ay/tpbsQaniu16cFxYGYNaqqVGft/oivecpdTDBbpPlsb8Q==} + '@tsc-ts/darwin-arm64@0.0.10': + resolution: {integrity: sha512-mRctKijCzWRxJgxiVAdsk+x9WB7kZz9GBbJ4eVP95bJFkIz9D11uw3xC0m6jWYNoaShF6zn48eLMTYOyJNxBdA==} cpu: [arm64] os: [darwin] - '@tsc-ts/darwin-x64@0.0.9': - resolution: {integrity: sha512-LHE74tzIEDtrEN8ENvCH/ejnnZjT0D2Vaz2HmggcvJudgr0W300CcwGf2Kdfw8MVBBk6JN0HPF9+a5AUUtFZGg==} + '@tsc-ts/darwin-x64@0.0.10': + resolution: {integrity: sha512-oV78/mi3ot8CG3BA9QxrsamvNXutpschgae+qV5skU4gAqimmBsBHy/MWTf9SCssvtWeqrX4uZvcwE0h/AJ+MQ==} cpu: [x64] os: [darwin] - '@tsc-ts/linux-arm64-gnu@0.0.9': - resolution: {integrity: sha512-dNiQIKApLcKIJPzb3pk2gfGrrgmOWa+q3/qjrx5ia/SzIWz0+lENlVvHiIn9ukqDacDE30FW8xHzby8Kyd+10A==} + '@tsc-ts/linux-arm64-gnu@0.0.10': + resolution: {integrity: sha512-O5msxFydn9r64QVm9WDWG8CKkXLjXWo12XeJWDncHlQYK80SoM4T8/C1UTDLtb6A2P+NQ5a+Xq4AaHmNb8VIqg==} cpu: [arm64] os: [linux] libc: [glibc] - '@tsc-ts/linux-arm64-musl@0.0.9': - resolution: {integrity: sha512-DolAqHtr1GUyG03Ch8+sJzpX1G+xzazAmj18kABzuOPre/6njrSWnmqJmtXQWvhwLBBEjajIkUJ4V+t5qigsrg==} + '@tsc-ts/linux-arm64-musl@0.0.10': + resolution: {integrity: sha512-v9N5HuqcEW97MzwzLqPNAN5tfAcXIAlLRtp7BSJJd5VKfBu6DplqNZzwCH+//NuAQOtWGSr/Dq565JFPmDRt5w==} cpu: [arm64] os: [linux] libc: [musl] - '@tsc-ts/linux-x64-gnu@0.0.9': - resolution: {integrity: sha512-leANVZ4tKz3myBSVogb173ttbAurPOOGjy1WeBlRhkVJ7zOPMiyzJi3cPOAK/4O8RcxXOsCiC2AfKPKDYM8bEw==} + '@tsc-ts/linux-x64-gnu@0.0.10': + resolution: {integrity: sha512-sRTPB/50uAJSpMjlA3sc9GTAySnLwIlf1txR8eHlcVpKE6q0QgWvEkKqCSGtFXkn8IQ8z8O68jrAk9WjmRTaYQ==} cpu: [x64] os: [linux] libc: [glibc] - '@tsc-ts/linux-x64-musl@0.0.9': - resolution: {integrity: sha512-a4FW16QXkrwNHAr4Qo/qDA5w9LxCJNrYbpqiz3TJ1sePTqvW/JTJc14tjx6avhW4mIaQp8AB52m7TqNAuDMm4Q==} + '@tsc-ts/linux-x64-musl@0.0.10': + resolution: {integrity: sha512-U4qZwP0JzrR/l4kzhh+w+y6o2guEEinC/wminfXXDhez2DtjNTOrM1GxMPfZ7JkCIcKEruL7iCC2telapFk5LQ==} cpu: [x64] os: [linux] libc: [musl] - '@tsc-ts/win32-x64-msvc@0.0.9': - resolution: {integrity: sha512-phThYnmOinlvY4dS4DEl1L9t/mFLiHR1zL5plSUfMB5NizA/jESfgMGRu+HW3uwOpCETSxgsuTNAXO7UuEIHIQ==} + '@tsc-ts/win32-x64-msvc@0.0.10': + resolution: {integrity: sha512-TsiK6k0ljFW3pNR5lvF7SL0nv3aGxr31ZbBCfIXcBrqAvpumINOy/G0ESfTvZWx8llwOQt3PxBS414gF/J0tFA==} cpu: [x64] os: [win32] @@ -2577,8 +2577,8 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} - tsc-ts@0.0.9: - resolution: {integrity: sha512-7fDfC/o1YoITG6sSXj4ZznijQYg8AfoCagmYvwG1BFLR/hz4Lwnq7jqDwcZfoDuPYTkrs8jHUTAVG1turniJHA==} + tsc-ts@0.0.10: + resolution: {integrity: sha512-oF5hZbeNgJXwJ868adnvBAihxYUwra1rj6Sa2+UhuhW477t5MmGnOsZgGTSGz511uTfuUxDz6e/gLIzvvScbGw==} engines: {node: '>=20'} hasBin: true @@ -3439,7 +3439,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true - '@scriptc/threads@0.4.2': {} + '@scriptc/threads@0.4.5': {} '@sinclair/typebox@0.25.24': {} @@ -3454,25 +3454,25 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 - '@tsc-ts/darwin-arm64@0.0.9': + '@tsc-ts/darwin-arm64@0.0.10': optional: true - '@tsc-ts/darwin-x64@0.0.9': + '@tsc-ts/darwin-x64@0.0.10': optional: true - '@tsc-ts/linux-arm64-gnu@0.0.9': + '@tsc-ts/linux-arm64-gnu@0.0.10': optional: true - '@tsc-ts/linux-arm64-musl@0.0.9': + '@tsc-ts/linux-arm64-musl@0.0.10': optional: true - '@tsc-ts/linux-x64-gnu@0.0.9': + '@tsc-ts/linux-x64-gnu@0.0.10': optional: true - '@tsc-ts/linux-x64-musl@0.0.9': + '@tsc-ts/linux-x64-musl@0.0.10': optional: true - '@tsc-ts/win32-x64-msvc@0.0.9': + '@tsc-ts/win32-x64-msvc@0.0.10': optional: true '@tybys/wasm-util@0.10.3': @@ -4813,17 +4813,17 @@ snapshots: ts-toolbelt@6.15.5: {} - tsc-ts@0.0.9: + tsc-ts@0.0.10: dependencies: - '@scriptc/threads': 0.4.2 + '@scriptc/threads': 0.4.5 optionalDependencies: - '@tsc-ts/darwin-arm64': 0.0.9 - '@tsc-ts/darwin-x64': 0.0.9 - '@tsc-ts/linux-arm64-gnu': 0.0.9 - '@tsc-ts/linux-arm64-musl': 0.0.9 - '@tsc-ts/linux-x64-gnu': 0.0.9 - '@tsc-ts/linux-x64-musl': 0.0.9 - '@tsc-ts/win32-x64-msvc': 0.0.9 + '@tsc-ts/darwin-arm64': 0.0.10 + '@tsc-ts/darwin-x64': 0.0.10 + '@tsc-ts/linux-arm64-gnu': 0.0.10 + '@tsc-ts/linux-arm64-musl': 0.0.10 + '@tsc-ts/linux-x64-gnu': 0.0.10 + '@tsc-ts/linux-x64-musl': 0.0.10 + '@tsc-ts/win32-x64-msvc': 0.0.10 tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c9f873c4ee..b678a87301 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,15 +7,15 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" - - "@tsc-ts/darwin-arm64@0.0.9" - - "@tsc-ts/darwin-x64@0.0.9" - - "@tsc-ts/linux-arm64-gnu@0.0.9" - - "@tsc-ts/linux-arm64-musl@0.0.9" - - "@tsc-ts/linux-x64-gnu@0.0.9" - - "@tsc-ts/linux-x64-musl@0.0.9" - - "@tsc-ts/win32-x64-msvc@0.0.9" - - "tsc-ts@0.0.9" - - "@scriptc/threads@0.4.2" + - "@tsc-ts/darwin-arm64@0.0.10" + - "@tsc-ts/darwin-x64@0.0.10" + - "@tsc-ts/linux-arm64-gnu@0.0.10" + - "@tsc-ts/linux-arm64-musl@0.0.10" + - "@tsc-ts/linux-x64-gnu@0.0.10" + - "@tsc-ts/linux-x64-musl@0.0.10" + - "@tsc-ts/win32-x64-msvc@0.0.10" + - "tsc-ts@0.0.10" + - "@scriptc/threads@0.4.5" allowBuilds: esbuild: true overrides: From a67f7d13eaa94842b914185102a5c856a058b82f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:23:47 -0500 Subject: [PATCH 17/49] Map wide shape-union records to their arm through one table per union A shape union's tag helper switched on the record's shape number. Inlined into a caller's branch on the arm, LLVM folded the two into one jump table per site, spanning the arms' shape numbers. In the native compiler, whose IR unions span about 112 shape numbers, that came to about 27,600 tables and 9 MB of read-only data, most of which a cold build pages in. A union whose arms span more than 32 shape numbers now maps the shape number to its arm through one constant table, indexed by the shape number minus the arms' lowest. An entry is a byte, or a 16-bit word for a union of 255 arms or more; the all-ones entry names no arm. Callers branch on the small arm tag. Narrower unions keep the switch, whose folded jump tables stay small and dispatch in one jump. --- .../src/backend/llvm/shape-union-tags.test.ts | 80 +++++++++++++++++++ packages/compiler/src/backend/llvm/shapes.ts | 74 +++++++++++++---- 2 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 packages/compiler/src/backend/llvm/shape-union-tags.test.ts diff --git a/packages/compiler/src/backend/llvm/shape-union-tags.test.ts b/packages/compiler/src/backend/llvm/shape-union-tags.test.ts new file mode 100644 index 0000000000..e07626e77a --- /dev/null +++ b/packages/compiler/src/backend/llvm/shape-union-tags.test.ts @@ -0,0 +1,80 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../../index.js"; +import { validateModule } from "../../ir/validate.js"; +import { emitLlvmModule } from "./emitter.js"; + +async function llvmFor(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-shape-union-tags-")); + try { + const entry = join(dir, "main.ts"), + output = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath: output, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); + const module = deserializeModule(await readFile(output, "utf8")); + expect(validateModule(module)).toEqual([]); + return emitLlvmModule(module); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function tagHelpers(ir: string): string[] { + return [...ir.matchAll(/^define internal i32 @sc_stag\.[^\n]*\{[^]*?^}/gm)].map( + (match) => match[0], + ); +} + +test("a union whose arms span few shape numbers switches on the shape word", async () => { + const ir = await llvmFor(` +type Num = { kind: "num"; value: number }; +type Neg = { kind: "neg"; operand: Node }; +type Add = { kind: "add"; left: Node; right: Node }; +type Node = Num | Neg | Add; +function evaluate(node: Node): number { + switch (node.kind) { + case "num": return node.value; + case "neg": return -evaluate(node.operand); + case "add": return evaluate(node.left) + evaluate(node.right); + } +} +const one: Node = { kind: "num", value: 1 }; +console.log(evaluate({ kind: "add", left: one, right: { kind: "neg", operand: one } })); +`); + expect(ir).not.toMatch(/@sc_stagtab\./); + const helpers = tagHelpers(ir); + expect(helpers.length).toBeGreaterThan(0); + for (const helper of helpers) expect(helper).toMatch(/switch i32 %w/); +}); + +test("a union whose arms span many shape numbers reads the arm from its table", async () => { + const arms = Array.from({ length: 40 }, (_, i) => i); + const ir = await llvmFor(` +${arms.map((i) => `type K${i} = { kind: "k${i}"; v${i}: number };`).join("\n")} +type Any = ${arms.map((i) => `K${i}`).join(" | ")}; +function value(item: Any): number { + switch (item.kind) { +${arms.map((i) => ` case "k${i}": return item.v${i};`).join("\n")} + } +} +const items: Any[] = [${arms.map((i) => `{ kind: "k${i}", v${i}: ${i} }`).join(", ")}]; +let total = 0; +for (const item of items) total += value(item); +console.log(total); +`); + // One byte per shape number from the arms' lowest to highest; 255 names no arm. + const table = + /^(@sc_stagtab\.\S+) = private unnamed_addr constant \[(\d+) x i8\] \[([^\]]*)\]$/m.exec(ir); + expect(table, "arm table").not.toBeNull(); + const entries = table![3]!.split(", ").map((entry) => Number(entry.replace("i8 ", ""))); + expect(entries).toHaveLength(Number(table![2])); + expect(entries.filter((entry) => entry !== 255).sort((a, b) => a - b)).toEqual(arms); + const helper = tagHelpers(ir).find((text) => text.includes(table![1]!)); + expect(helper, "tag helper").toBeDefined(); + expect(helper).toContain(`getelementptr inbounds [${table![2]} x i8], ptr ${table![1]}`); + expect(helper).not.toMatch(/switch i32 %w/); +}); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 6d03db222e..79a79f36ce 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -313,19 +313,46 @@ function shapeUnionRc(host: ShapeHost, union: ShapeUnion): { retain: string; rel }; } +/** The widest span of shape numbers a union's arms may cover for its tag + * helper to switch on the shape word (emitShapeUnionHelpers). */ +const SHAPE_TAG_SWITCH_SPAN = 32; + /** The tag, release and trace helpers of every shape union * (nullable-unions.ts). The tag helper maps a value to its arm: NULL and * the null sentinel are the unit arms, anything else a tagged record whose - * shape word names its arm (the switch folds to a lookup table). */ + * shape word names its arm. + * - Arms within SHAPE_TAG_SWITCH_SPAN shape numbers: the helper switches on + * the shape word. Inlined, the switch folds into the caller's branch on + * the arm, one indirect jump with a jump table of at most that many + * entries per site. + * - Wider unions: the shape word indexes the union's arm table, one entry + * per shape number from the arms' lowest to highest, and callers branch + * on the small arm tag. Folded switches would give every site of such a + * union a jump table spanning all of its arms' shape numbers. */ export function emitShapeUnionHelpers(host: ShapeHost): string[] { const out: string[] = []; const S = host.sizeType; for (const union of host.nullableUnions.allShapeUnions()) { const header = shapeWordHeader(S); const tagFn = shapeTagFn(union.unionId); - const cases = union.arms - .map((arm) => `i32 ${host.nullableUnions.shapeNumber(arm.shapeId)}, label %a${arm.tag}`) - .join(" "); + const numbers = union.arms.map((arm) => host.nullableUnions.shapeNumber(arm.shapeId)); + const lo = Math.min(...numbers); + const span = Math.max(...numbers) - lo + 1; + const byTable = span > SHAPE_TAG_SWITCH_SPAN; + // Entries are bytes, or 16-bit words for a union of 255 arms or more. + // A shape number that names no arm maps to the all-ones entry, which is + // above every arm tag, so callers take their path for no arm as they do + // for the -1 of a shape number outside the table. + const wide = union.arms.some((arm) => arm.tag >= 255); + const entry = wide ? "i16" : "i8"; + const none = wide ? 65535 : 255; + const table = new Array(span).fill(none); + union.arms.forEach((arm, i) => (table[numbers[i]! - lo] = arm.tag)); + const tableName = tagFn.replace("@sc_stag.", "@sc_stagtab."); + if (byTable) + out.push( + `${tableName} = private unnamed_addr constant [${span} x ${entry}] [${table.map((t) => `${entry} ${t}`).join(", ")}]`, + ); out.push( `define internal i32 ${tagFn}(ptr %u) alwaysinline ${FN_ATTRS} { ; shape union tag`, `entry:`, @@ -343,16 +370,35 @@ export function emitShapeUnionHelpers(host: ShapeHost): string[] { ` ret i32 ${union.nullTag}`, `rec:`, ); - out.push( - ` %wp = getelementptr inbounds ${header}, ptr %u, i32 0, i32 ${SHAPE_WORD_INDEX}`, - ` %w = load i32, ptr %wp`, - ` switch i32 %w, label %bad [ ${cases} ]`, - ...union.arms.flatMap((arm) => [`a${arm.tag}:`, ` ret i32 ${arm.tag}`]), - `bad:`, - ` ret i32 -1`, - `}`, - ``, - ); + const cases = union.arms.map((arm, i) => `i32 ${numbers[i]}, label %a${arm.tag}`).join(" "); + if (!byTable) + out.push( + ` %wp = getelementptr inbounds ${header}, ptr %u, i32 0, i32 ${SHAPE_WORD_INDEX}`, + ` %w = load i32, ptr %wp`, + ` switch i32 %w, label %bad [ ${cases} ]`, + ...union.arms.flatMap((arm) => [`a${arm.tag}:`, ` ret i32 ${arm.tag}`]), + `bad:`, + ` ret i32 -1`, + `}`, + ``, + ); + else + out.push( + ` %wp = getelementptr inbounds ${header}, ptr %u, i32 0, i32 ${SHAPE_WORD_INDEX}`, + ` %w = load i32, ptr %wp`, + ` %i = sub i32 %w, ${lo}`, + ` %inrange = icmp ult i32 %i, ${span}`, + ` br i1 %inrange, label %look, label %bad`, + `look:`, + ` %tp = getelementptr inbounds [${span} x ${entry}], ptr ${tableName}, i32 0, i32 %i`, + ` %t8 = load ${entry}, ptr %tp`, + ` %tag = zext ${entry} %t8 to i32`, + ` ret i32 %tag`, + `bad:`, + ` ret i32 -1`, + `}`, + ``, + ); // Release and trace: one definition for the unions over the same // record arms (helperOwner). Unit encodings are NULL or the immortal // null sentinel; the shape word picks the arm. From 7373d3c776a08e1cd17e6d1f3b920f1e4cd375e0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:25:51 -0500 Subject: [PATCH 18/49] Attribute a derived class's rejection to its rejected base A class extending a program class whose own declaration was rejected (a degradable field type) failed collection with an inheritance error that carried no cause. The localized-failure report then counted the derived class, and the uses of it, as remaining blockers instead of the base field's cascade. The error now names the base's failure group as its cause. --- .../src/frontend/lowering/lower-classes.ts | 22 +++++++++++++++++-- tests/coverage-fixtures/localized-failure.ts | 15 +++++++++++++ .../coverage-explain-localized-failure.txt | 19 ++++++++++++---- tests/harness/coverage.test.ts | 8 ++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 2c0145c340..c0a79e1c0d 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -116,6 +116,7 @@ import { requireSpecOf, } from "../program.js"; import { PoisonError, dynFallbackType, jsFuncNameOf, newFnCtx, own } from "./lowerer.js"; +import { declarationGroupKey } from "./type-failure.js"; import { isAssertClassNew } from "./assert-instances.js"; import { isCjsRequireGlobal } from "./lower-node-module.js"; import { lowerArrayConstructor } from "./containers/array-construction.js"; @@ -2297,11 +2298,17 @@ export function collectClassShapeInner( base = named; if (!base) { if (adoptCallableBase(t.expression)) continue; - lowerer.unsupported( + const diag = unsupportedDiag( "SC1090", - t, + locOf(t), `extending classes not declared in the program ('${t.expression.text}')`, ); + // A base the program declares but rejected: this rejection is its + // cascade (localized failure, report-only). + const rejectedBase = rejectedProgramClassGroup(lowerer, symbol); + if (rejectedBase !== null) diag.cause = [rejectedBase]; + lowerer.pushDiag(diag); + throw new PoisonError(); } } @@ -11847,3 +11854,14 @@ function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaratio }); return found; } + +/** The failure group of a class the program declares but did not collect + * (its own declaration was rejected), or null for any other symbol. */ +function rejectedProgramClassGroup(lowerer: Lowerer, symbol: ts.Symbol | null): string | null { + if (symbol === null) return null; + const recorded = lowerer.rejectedGroups.get(symbol); + if (recorded !== undefined) return recorded; + const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; + return declarationGroupKey(symbol); +} diff --git a/tests/coverage-fixtures/localized-failure.ts b/tests/coverage-fixtures/localized-failure.ts index 84ae343b46..04a5b8037e 100644 --- a/tests/coverage-fixtures/localized-failure.ts +++ b/tests/coverage-fixtures/localized-failure.ts @@ -51,3 +51,18 @@ console.log(s.count(), s.label()); console.log(listAll("abc")); console.log(new Holder().get()); console.log(pick(1), new Cache().size()); + +// A class extending one the dyn class field policy rejects: its rejection +// and the uses of the derived class are that field's cascade. +class Base { + state: any = {}; + get(): number { + return 1; + } +} +class Derived extends Base { + twice(): number { + return 2; + } +} +console.log(new Derived().twice()); diff --git a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt index ed727b6048..567226f0a1 100644 --- a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt +++ b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt @@ -1,12 +1,14 @@ dynamic tier (tier assignment, report-only; --tier=auto) 0 functions already operate on dynamic values (0 statements) - 11 demotion requests (refusals and fences the dynamic tier would replace) + 14 demotion requests (refusals and fences the dynamic tier would replace) - 4 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) - 11 errors: 9 clear with declaration slots (L1-L2), 9 with member and expression slots too (L3-L4); 6 cascades attributed; 1 type failure stays refused + 5 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) + 14 errors: 12 clear with declaration slots (L1-L2), 12 with member and expression slots too (L3-L4); 8 cascades attributed; 1 type failure stays refused L2 field tools: Tool[] tests/coverage-fixtures/localized-failure.ts:13:11 (SC2009 component, clears 2) rejects class Session + L2 field state: any tests/coverage-fixtures/localized-failure.ts:58:3 (SC1090 dyn-field-policy, clears 2) + rejects class Base L2 param progress: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:23:31 (SC2009 component, clears 1) rejects function listAll L2 field state: any tests/coverage-fixtures/localized-failure.ts:29:3 (SC1090 dyn-field-policy, clears 1) @@ -22,6 +24,12 @@ class Cache tests/coverage-fixtures/localized-failure.ts:42:1 (1 request) <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:53:22: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet -> function turns dynamic + class Base tests/coverage-fixtures/localized-failure.ts:57:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:63:23: extending classes not declared in the program ('Base') is not supported yet + -> function turns dynamic + class Derived tests/coverage-fixtures/localized-failure.ts:63:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:68:13: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic field Session.tools tests/coverage-fixtures/localized-failure.ts:13:3 (1 request) <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:13:11: values of type 'Tool[]' cannot be compiled: the array shape is supported, but its element type 'Tool' does not compile -> function turns dynamic @@ -31,7 +39,10 @@ field Cache.tokens tests/coverage-fixtures/localized-failure.ts:43:3 (1 request) <- SC2020 operation at tests/coverage-fixtures/localized-failure.ts:43:3: 'WeakRef' is part of the standard library types but has no scriptc lowering yet -> function turns dynamic - function tests/coverage-fixtures/localized-failure.ts:1:1 (8 sites inside) + field Base.state tests/coverage-fixtures/localized-failure.ts:58:3 (1 request) + <- SC1090 operation at tests/coverage-fixtures/localized-failure.ts:58:3: 'any'-typed class fields are not supported yet + -> function turns dynamic + function tests/coverage-fixtures/localized-failure.ts:1:1 (11 sites inside) function listAll tests/coverage-fixtures/localized-failure.ts:23:1 (1 request, 1 site inside) <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:51:13: uses of 'listAll' inherit the blocker on its declaration -> function turns dynamic diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 3f9a60bcea..f4e87c89ee 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -431,8 +431,8 @@ test("--explain-dynamic attributes each refusal to the unit the dynamic tier wou test("--explain-dynamic lists the type failures localized failure would turn into dynamic slots", async () => { const { coverage } = analyze(fixture("localized-failure.ts"), { explainDynamic: true }); const report = coverage.tiers!.localizedFailure; - expect(report.errors).toBe(11); - expect(report.typeFailures).toEqual({ degradable: 4, refused: 1 }); + expect(report.errors).toBe(14); + expect(report.typeFailures).toEqual({ degradable: 5, refused: 1 }); expect( report.slots.map((s) => [ s.stage, @@ -444,13 +444,15 @@ test("--explain-dynamic lists the type failures localized failure would turn int ]), ).toEqual([ ["L2", "field", "tools", "component", "class Session", 2], + // The derived class's rejection and its construction. + ["L2", "field", "state", "dyn-field-policy", "class Base", 2], ["L2", "param", "progress", "component", "function listAll", 1], ["L2", "field", "state", "dyn-field-policy", "class Holder", 1], ["L1", "local", "callbacks", "component", "statement", 1], ]); // The WeakRef field stays refused, so its class and the construction // that uses it stay errors. - expect(report.clearedThroughL2).toBe(9); + expect(report.clearedThroughL2).toBe(12); expect(report.remainingByCode).toEqual({ SC1090: 1, SC2020: 1 }); const relative = JSON.parse( JSON.stringify(coverage.tiers).replaceAll(repoRoot + "/", ""), From 53ebf05ff587b9d28a1b28f048c4815b30d1fc25 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 19:35:12 -0700 Subject: [PATCH 19/49] Add the canonical type normalizer, not yet wired into mapping Stage N0 of the localized-failure design (part 2, canonical type normalization), report-only. canon(type, env) resolves a checker type under generic bindings to a small set of canonical shapes: primitive, literal, unit, top, object (nominal for classes and standard-library interfaces, structural otherwise, with members resolved lazily through the checker), array, tuple, flat union, unbound parameter, and an opaque leaf. Intersections whose constituents agree, or that are all object-like, resolve to one object; a branded primitive is the primitive; T & {} drops T's unit arms. Type parameters substitute from the environment, so a generic alias spelled over a generic body's own parameters resolves without asking the checker to instantiate, and the alias reference keeps the original checker type. Nodes intern by type and bindings, so recursion closes on the node under construction. Nothing calls it yet: N1 runs it in shadow mode beside mapType. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/canon.ts | 310 ++++++++++++++++++ .../compiler/test/canon-normalize.test.ts | 160 +++++++++ 2 files changed, 470 insertions(+) create mode 100644 packages/compiler/src/frontend/canon.ts create mode 100644 packages/compiler/test/canon-normalize.test.ts diff --git a/packages/compiler/src/frontend/canon.ts b/packages/compiler/src/frontend/canon.ts new file mode 100644 index 0000000000..833d130813 --- /dev/null +++ b/packages/compiler/src/frontend/canon.ts @@ -0,0 +1,310 @@ +/* Canonical type normalization (localized-failure design part 2, §4), + * stage N0: report-only. Nothing in the compiler consumes it yet. + * + * `canon(type, env)` resolves a checker type, under the current generic + * bindings, to a small set of canonical shapes: primitive, literal, unit, + * top, object (nominal or structural, with lazily resolved members), array, + * tuple, flat union, unbound parameter, and an opaque leaf for what it + * cannot reduce. The per-form rules of type-mapper.ts (generic aliases, + * equal-member intersections, narrowed parameters, recursive placeholders) + * are what this replaces family by family in later stages; N1 runs it in + * shadow mode beside mapType, N2 as mapType's fallback. + * + * Nodes intern by checker type identity and the bindings of the type + * parameters the walk consulted, so a recursive type closes on itself once + * (a back-reference returns the node under construction). */ +import * as ts from "./ts7/adapter.js"; + +export type CanonPrim = "number" | "string" | "boolean" | "bigint" | "symbol"; + +export interface CanonProp { + name: string; + optional: boolean; + readonly: boolean; + type: () => Canon; +} + +export interface CanonSig { + params: { name: string; optional: boolean; rest: boolean; type: () => Canon }[]; + ret: () => Canon; + /** The signature keeps type parameters of its own. */ + generic: boolean; +} + +export interface CanonIndex { + key: Canon; + value: () => Canon; +} + +export interface AliasRef { + name: string; + args: readonly Canon[]; + /** The checker type the alias reference came from (identity preserved + * for tables keyed by ts.Type, such as boundAliasInstances). */ + tsType: ts.Type; +} + +export type Canon = + | { k: "prim"; p: CanonPrim } + | { k: "lit"; value: string | number | boolean } + | { k: "unit"; u: "undefined" | "null" | "void" | "never" } + | { k: "top"; t: "unknown" | "any" | "object" } + | { + k: "obj"; + /** Class or standard-library declaration name, when nominal. */ + nominal?: string; + alias?: AliasRef; + props: () => readonly CanonProp[]; + index: () => readonly CanonIndex[]; + call: () => readonly CanonSig[]; + construct: () => readonly CanonSig[]; + } + | { k: "array"; elem: Canon; readonly: boolean } + | { k: "tuple"; elems: readonly { type: Canon; optional: boolean; rest: boolean }[] } + | { k: "union"; arms: readonly Canon[] } + | { k: "param"; name: string } + | { k: "opaque"; ts: ts.Type; reason: string }; + +/** Type-parameter bindings: the checker type a parameter is bound to. */ +export type CanonEnv = ReadonlyMap; + +const PRIM_FLAGS: readonly [number, CanonPrim][] = [ + [ts.TypeFlags.Number, "number"], + [ts.TypeFlags.String, "string"], + [ts.TypeFlags.Boolean, "boolean"], + [ts.TypeFlags.BigInt, "bigint"], + [ts.TypeFlags.ESSymbol | ts.TypeFlags.UniqueESSymbol, "symbol"], +]; + +/** A stable key for a canonical node, for deduplication and tests. */ +export function canonKey(c: Canon, depth = 0): string { + if (depth > 6) return "…"; + switch (c.k) { + case "prim": + return c.p; + case "lit": + return JSON.stringify(c.value); + case "unit": + return c.u; + case "top": + return c.t; + case "param": + return `param ${c.name}`; + case "opaque": + return `opaque(${c.reason})`; + case "array": + return `${c.readonly ? "readonly " : ""}${canonKey(c.elem, depth + 1)}[]`; + case "tuple": + return `[${c.elems.map((e) => `${e.rest ? "..." : ""}${canonKey(e.type, depth + 1)}${e.optional ? "?" : ""}`).join(", ")}]`; + case "union": + return c.arms.map((a) => canonKey(a, depth + 1)).join(" | "); + case "obj": { + if (c.nominal !== undefined) return c.nominal; + const props = c + .props() + .map((p) => `${p.name}${p.optional ? "?" : ""}: ${canonKey(p.type(), depth + 1)}`); + const calls = c.call().map((s) => `(${s.params.length}) => ${canonKey(s.ret(), depth + 1)}`); + return `{ ${[...calls, ...props].join("; ")} }`; + } + } +} + +export class Normalizer { + private readonly interned = new Map(); + + constructor(private readonly checker: ts.TypeChecker) {} + + canon(type: ts.Type, env: CanonEnv = new Map()): Canon { + const id = (type as { id?: number }).id; + const key = id === undefined ? null : `${id}|${this.envKey(env)}`; + if (key !== null) { + const known = this.interned.get(key); + if (known !== undefined) return known; + } + const node = this.build(type, env, key); + if (key !== null && !this.interned.has(key)) this.interned.set(key, node); + return node; + } + + private envKey(env: CanonEnv): string { + if (env.size === 0) return ""; + const parts: string[] = []; + for (const [param, bound] of env) + parts.push(`${ts.symbolName(param)}=${(bound as { id?: number }).id ?? "?"}`); + return parts.sort().join(","); + } + + private build(type: ts.Type, env: CanonEnv, key: string | null): Canon { + const flags = type.flags; + if (flags & ts.TypeFlags.Any) return { k: "top", t: "any" }; + if (flags & ts.TypeFlags.Unknown) return { k: "top", t: "unknown" }; + if (flags & ts.TypeFlags.NonPrimitive) return { k: "top", t: "object" }; + if (flags & ts.TypeFlags.Never) return { k: "unit", u: "never" }; + if (flags & ts.TypeFlags.Void) return { k: "unit", u: "void" }; + if (flags & ts.TypeFlags.Undefined) return { k: "unit", u: "undefined" }; + if (flags & ts.TypeFlags.Null) return { k: "unit", u: "null" }; + if (flags & ts.TypeFlags.TemplateLiteral) return { k: "prim", p: "string" }; + if (flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral)) { + const value = (type as { value?: string | number }).value; + if (value !== undefined) return { k: "lit", value }; + } + if (flags & ts.TypeFlags.BooleanLiteral) + return { k: "lit", value: this.checker.typeToString(type) === "true" }; + if (!type.isUnionType()) + for (const [flag, p] of PRIM_FLAGS) if (flags & flag) return { k: "prim", p }; + if (flags & ts.TypeFlags.TypeParameter) { + const symbol = type.getSymbol(); + const bound = symbol === undefined ? undefined : env.get(symbol); + if (bound !== undefined && bound !== type) return this.canon(bound, env); + return { k: "param", name: symbol === undefined ? "?" : ts.symbolName(symbol) }; + } + if (type.isUnionType()) return this.union(ts.constituentTypes(type), env); + if (type.isIntersectionType()) return this.intersection(type, env, key); + if (flags & (ts.TypeFlags.Conditional | ts.TypeFlags.IndexedAccess | ts.TypeFlags.Index)) + return { k: "opaque", ts: type, reason: "symbolic type operator" }; + if (flags & ts.TypeFlags.Object) return this.object(type, env, key); + return { k: "opaque", ts: type, reason: `flags ${flags}` }; + } + + /** Flat, deduplicated, never-free; `void` reads as `undefined`. */ + private union(parts: readonly ts.Type[], env: CanonEnv): Canon { + const arms: Canon[] = []; + const seen = new Set(); + const add = (c: Canon): void => { + if (c.k === "union") { + for (const a of c.arms) add(a); + return; + } + if (c.k === "unit" && c.u === "never") return; + const arm: Canon = c.k === "unit" && c.u === "void" ? { k: "unit", u: "undefined" } : c; + // Objects dedupe by identity (they intern by checker type): their + // structural key would walk into a recursive type still being built. + const k = arm.k === "obj" ? arm : canonKey(arm); + if (seen.has(k)) return; + seen.add(k); + arms.push(arm); + }; + for (const part of parts) add(this.canon(part, env)); + // Units last, in a fixed order; the rest keep the checker's order. + const unitRank = (c: Canon): number => + c.k !== "unit" ? 0 : c.u === "null" ? 1 : c.u === "undefined" ? 2 : 3; + arms.sort((a, b) => unitRank(a) - unitRank(b)); + if (arms.length === 0) return { k: "unit", u: "never" }; + if (arms.length === 1) return arms[0]!; + // `true | false` is `boolean`. + const bools = arms.filter((a) => a.k === "lit" && typeof a.value === "boolean"); + if (bools.length === 2) { + const rest = arms.filter((a) => !(a.k === "lit" && typeof a.value === "boolean")); + const merged: Canon[] = [{ k: "prim", p: "boolean" }, ...rest]; + return merged.length === 1 ? merged[0]! : { k: "union", arms: merged }; + } + return { k: "union", arms }; + } + + /** Equal constituents collapse; object-like constituents without + * signatures merge through the checker's resolved properties; a + * primitive branded with an object is the primitive. */ + private intersection(type: ts.Type, env: CanonEnv, key: string | null): Canon { + const parts = ts.constituentTypes(type).map((t) => this.canon(t, env)); + const keys = new Set(parts.map((p) => canonKey(p))); + if (keys.size === 1) return parts[0]!; + // Narrowed parameters: `T & {}` (and `T & ({} | null)`'s object part) + // keep T without its undefined and null arms. + const empty = (p: Canon): boolean => + p.k === "obj" && p.nominal === undefined && p.props().length === 0 && p.call().length === 0; + const rest = parts.filter((p) => !empty(p)); + if (rest.length === 1 && rest.length < parts.length) { + const only = rest[0]!; + if (only.k !== "union") return only.k === "unit" ? { k: "unit", u: "never" } : only; + const arms = only.arms.filter((a) => a.k !== "unit"); + return arms.length === 1 ? arms[0]! : { k: "union", arms }; + } + const prims = parts.filter((p) => p.k === "prim" || p.k === "lit"); + if (prims.length === 1 && parts.every((p) => p === prims[0] || p.k === "obj")) return prims[0]!; + if (parts.every((p) => p.k === "obj" || (p.k === "top" && p.t === "object"))) + return this.object(type, env, key); + return { k: "opaque", ts: type, reason: "intersection" }; + } + + private object(type: ts.Type, env: CanonEnv, key: string | null): Canon { + const checker = this.checker; + if (checker.isArrayType(type)) { + const [elem] = checker.getTypeArguments(type as ts.TypeReference); + return { + k: "array", + elem: elem === undefined ? { k: "top", t: "unknown" } : this.canon(elem, env), + readonly: + type.getSymbol() !== undefined && ts.symbolName(type.getSymbol()!) === "ReadonlyArray", + }; + } + if (checker.isTupleType(type)) { + const args = checker.getTypeArguments(type as ts.TypeReference); + return { + k: "tuple", + elems: args.map((a) => ({ type: this.canon(a, env), optional: false, rest: false })), + }; + } + const symbol = type.getSymbol(); + const decl = symbol?.declarations[0]; + const nominal = + decl !== undefined && (ts.isClassDeclaration(decl) || ts.isClassExpression(decl)) + ? ts.symbolName(symbol!) + : decl !== undefined && + ts.isInterfaceDeclaration(decl) && + ts.getSourceFileOfNode(decl).isDeclarationFile && + /\/lib\.[^/]*\.d\.ts$/.test(ts.getSourceFileOfNode(decl).fileName) + ? ts.symbolName(symbol!) + : undefined; + const aliasSymbol = type.getAliasSymbol(); + const alias: AliasRef | undefined = + aliasSymbol === undefined + ? undefined + : { + name: ts.symbolName(aliasSymbol), + args: type.getAliasTypeArguments().map((a) => this.canon(a, env)), + tsType: type, + }; + let props: readonly CanonProp[] | undefined; + let index: readonly CanonIndex[] | undefined; + let call: readonly CanonSig[] | undefined; + let construct: readonly CanonSig[] | undefined; + const sigs = (list: readonly ts.Signature[]): CanonSig[] => + list.map((s) => ({ + params: s.getParameters().map((p) => { + const pd = p.declarations[0]; + const param = pd !== undefined && ts.isParameter(pd) ? pd : undefined; + return { + name: ts.symbolName(p), + optional: param?.questionToken !== undefined || param?.initializer !== undefined, + rest: param?.dotDotDotToken !== undefined, + type: () => this.canon(checker.getTypeOfSymbol(p), env), + }; + }), + ret: () => this.canon(checker.getReturnTypeOfSignature(s), env), + generic: s.getTypeParameters().length > 0, + })); + const node: Canon = { + k: "obj", + ...(nominal !== undefined ? { nominal } : {}), + ...(alias !== undefined ? { alias } : {}), + props: () => + (props ??= checker.getPropertiesOfType(type).map((p) => ({ + name: ts.symbolName(p), + optional: (p.flags & ts.SymbolFlags.Optional) !== 0, + readonly: false, + type: () => this.canon(checker.getTypeOfSymbol(p), env), + }))), + index: () => + (index ??= checker.getIndexInfosOfType(type).map((info) => ({ + key: this.canon(info.keyType, env), + value: () => this.canon(info.type, env), + }))), + call: () => (call ??= sigs(checker.getCallSignatures(type))), + construct: () => (construct ??= sigs(checker.getConstructSignatures(type))), + }; + // Recursion closes here: members resolve lazily, so a back-reference + // reaches this node through the intern table. + if (key !== null) this.interned.set(key, node); + return node; + } +} diff --git a/packages/compiler/test/canon-normalize.test.ts b/packages/compiler/test/canon-normalize.test.ts new file mode 100644 index 0000000000..c432419b78 --- /dev/null +++ b/packages/compiler/test/canon-normalize.test.ts @@ -0,0 +1,160 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import { canonKey, Normalizer, type Canon } from "../src/frontend/canon.js"; +import { loadProgram } from "../src/frontend/program-node.js"; +import * as ts from "../src/frontend/ts7/adapter.js"; + +let directory: string; +let load: ReturnType; +let checker: ts.TypeChecker; +const aliases = new Map(); +const asTypes = new Map(); +const valueTypes = new Map(); + +beforeAll(() => { + directory = mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-canon-")); + const entry = join(directory, "input.ts"); + writeFileSync( + entry, + ` + // #2228: an intersection that restates an optional array member. + export interface BuildOptions { cwd: string; contextFiles?: Array<{ path: string; content: string }> } + export type Normalized = BuildOptions & { contextFiles: Array<{ path: string; content: string }> }; + export type Same = { x: number[] } & { x: number[] }; + // Unions flatten, drop never, read void as undefined, and dedupe. + export type Flat = string | (number | undefined) | never | void | string; + export type Flag = true | false | 0; + // Branded primitive. + export type Id = string & { readonly brand: "id" }; + // #2241/#2246: a recursive interface with a generic method. + export interface TreeNode { name: string; child?: TreeNode; send(message: T): string } + // #2227: a generic alias spelled over a generic body's own parameters. + export type Catalog = { groups: G[]; provider: P }; + export function cast(x: unknown) { + return x as Catalog; + } + // Narrowed type parameters. + export function present(x: T & {}) { + return x; + } + export const sampleNumber = 1 as number; + export const sampleText = "a" as string; + export const sampleMaybe = undefined as string | undefined; + export class Point { x = 0 } + export type Points = readonly Point[]; + export type Pair = [string, number]; + `, + ); + load = loadProgram(entry); + checker = load.program.getTypeChecker(); + ts.walkPreorder(load.entry, (node) => { + if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) + aliases.set( + node.name.text, + checker.getTypeAtLocation(ts.isTypeAliasDeclaration(node) ? node.type : node.name), + ); + if (ts.isAsExpression(node)) { + let fn: ts.Node | undefined = node.parent; + while (fn !== undefined && !ts.isFunctionDeclaration(fn)) fn = fn.parent; + const params = + fn !== undefined && ts.isFunctionDeclaration(fn) + ? (fn.typeParameters?.nodes ?? []).map((p) => checker.getSymbolAtLocation(p.name)!) + : []; + asTypes.set(ts.getTextOfNode(node.type), { + type: checker.getTypeAtLocation(node.type), + params, + }); + } + if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) + valueTypes.set(node.name.text, checker.getTypeAtLocation(node.name)); + if ( + ts.isParameter(node) && + node.type !== undefined && + ts.isFunctionDeclaration(node.parent) && + node.parent.name?.text === "present" + ) + asTypes.set("present", { + type: checker.getTypeAtLocation(node.type), + params: (node.parent.typeParameters?.nodes ?? []).map((p) => + checker.getSymbolAtLocation(p.name)!, + ), + }); + }); +}); +afterAll(() => { + load?.dispose(); + if (directory) rmSync(directory, { recursive: true, force: true }); +}); + +const key = (c: Canon) => canonKey(c); + +describe("canon", () => { + test("intersections that restate members resolve to one object (#2228)", () => { + const n = new Normalizer(checker); + expect(key(n.canon(aliases.get("Same")!))).toBe("{ x: number[] }"); + expect(key(n.canon(aliases.get("Normalized")!))).toBe( + "{ cwd: string; contextFiles: { path: string; content: string }[] }", + ); + }); + + test("unions are flat, never-free and deduplicated", () => { + const n = new Normalizer(checker); + expect(key(n.canon(aliases.get("Flat")!))).toBe("string | number | undefined"); + expect(key(n.canon(aliases.get("Flag")!))).toBe("boolean | 0"); + }); + + test("a branded primitive is the primitive", () => { + expect(key(new Normalizer(checker).canon(aliases.get("Id")!))).toBe("string"); + }); + + test("recursion closes on the node under construction (#2241, #2246)", () => { + const n = new Normalizer(checker); + const tree = n.canon(aliases.get("TreeNode")!); + expect(tree.k).toBe("obj"); + if (tree.k !== "obj") return; + const child = tree + .props() + .find((p) => p.name === "child")! + .type(); + expect(child.k).toBe("union"); + if (child.k !== "union") return; + expect(child.arms).toContain(tree); + const send = tree + .props() + .find((p) => p.name === "send")! + .type(); + expect(send.k === "obj" && send.call()[0]!.generic).toBe(true); + }); + + test("a generic alias in a generic body substitutes the environment (#2227)", () => { + const n = new Normalizer(checker); + const { type, params } = asTypes.get("Catalog")!; + expect(key(n.canon(type))).toBe("{ groups: param G[]; provider: param P }"); + const bound = n.canon( + type, + new Map([ + [params[0]!, valueTypes.get("sampleNumber")!], + [params[1]!, valueTypes.get("sampleText")!], + ]), + ); + expect(key(bound)).toBe("{ groups: number[]; provider: string }"); + expect(bound.k === "obj" && bound.alias?.name).toBe("Catalog"); + expect(bound.k === "obj" && bound.alias?.tsType).toBe(type); + }); + + test("a narrowed parameter drops its unit arms", () => { + const n = new Normalizer(checker); + const { type, params } = asTypes.get("present")!; + expect(key(n.canon(type, new Map([[params[0]!, valueTypes.get("sampleMaybe")!]])))).toBe( + "string", + ); + }); + + test("classes are nominal; arrays and tuples keep their elements", () => { + const n = new Normalizer(checker); + expect(key(n.canon(aliases.get("Points")!))).toBe("readonly Point[]"); + expect(key(n.canon(aliases.get("Pair")!))).toBe("[string, number]"); + }); +}); From a76b7e77b410dc459c6826a745ff1e12d1e65471 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:37:55 -0500 Subject: [PATCH 20/49] Address review feedback --- .../src/frontend/lowering/lower-stmts.ts | 7 +++++++ .../localized-failure-forward-capture.ts.json | 6 ++++++ .../localized-failure-forward-capture.ts | 19 +++++++++++++++++++ .../localized-failure-forward-capture.ts.json | 1 + 4 files changed, 33 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json create mode 100644 tests/corpus/localized-failure-forward-capture.ts create mode 100644 tests/harness/corpus-costs/localized-failure-forward-capture.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 7d5273e074..6e3a7a8de2 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -755,6 +755,10 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir const touched = new Set(); lowerer.failureTouches.push(touched); const pendingSlots: DynamicSlot[] = []; + // A declarator that initializes a forward-captured TDZ box takes it + // out of tdzPredeclared; a retry after a later declarator degrades + // must initialize the same box again. + const tdzBefore = new Map(lowerer.ctx.tdzPredeclared); try { for (let attempt = 0; ; attempt++) { try { @@ -773,6 +777,9 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // forward captures declared (they stay declared), so the // retry appends after them. pendingSlots.push(slot); + for (const [symbol, local] of tdzBefore) + if (!lowerer.ctx.tdzPredeclared.has(symbol)) + lowerer.ctx.tdzPredeclared.set(symbol, local); } } for (const slot of pendingSlots) lowerer.noteDynamicSlot(slot); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json new file mode 100644 index 0000000000..f775701663 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-forward-capture.ts" + ], + "diags": [] +} diff --git a/tests/corpus/localized-failure-forward-capture.ts b/tests/corpus/localized-failure-forward-capture.ts new file mode 100644 index 0000000000..ba43a037d7 --- /dev/null +++ b/tests/corpus/localized-failure-forward-capture.ts @@ -0,0 +1,19 @@ +// Localized failure, stage L1: a statement that declares a binding an +// earlier closure captures, and then a binding whose declared type has no +// static lowering, still initializes the captured binding. +type Callbacks = ((done: number) => void) | AbortSignal | Map string> | Set; + +function run(): void { + const read = (): number => total; + const total = 3, + hook: Callbacks = new Set([1, 2]); + console.log(read(), hook instanceof Set, hook instanceof Set && hook.size); + let late = (): string => label; + let label = "first", + other: Callbacks = new Set([3]); + console.log(late(), other instanceof Set); + label = "second"; + late = (): string => label + "!"; + console.log(late()); +} +run(); diff --git a/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json b/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 19f51465f36edda613a0c1fe0f360974b6af7bb4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 19:37:58 -0700 Subject: [PATCH 21/49] Format the class id bound check Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/backend/llvm/classes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index f71f96a3d5..74880453f7 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -586,7 +586,8 @@ export const LAYOUT_ID_LIMIT = 0x3fffffff; * (LAYOUT_ID_LIMIT), so it never sets the program-lifetime or frozen bit * and the runtime reads it back (scr_rc.h scr_rc_layout_id). */ export function classIdRcWord(meta: LlClassMeta, lifetime: boolean): string { - if (meta.pre + 1 >= LAYOUT_ID_LIMIT) throw new Error(`class id ${meta.pre + 1} is not a layout id`); + if (meta.pre + 1 >= LAYOUT_ID_LIMIT) + throw new InternalCompilerError(`class id ${meta.pre + 1} is not a layout id`); return (BigInt(meta.pre + 1) * 2n ** 32n + (lifetime ? 2n ** 31n : 1n)).toString(); } From 23deb595ac221b179215640b75873969dae9c8f6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 21:41:36 -0500 Subject: [PATCH 22/49] Relocate the prebuilt lib diagnostics path when installing the native command The native toolchain manifest's lib_diagnostics path is relative to the platform package's dist/bin. installNativeCli copies the manifest into the scriptc package's bin and relocates each asset path, but lib_diagnostics was not in the list, so it pointed at a missing file under the scriptc package and npm installs did not use the prebuilt lib diagnostics. It is now relocated with the other assets. --- packages/cli/scripts/install-native.mjs | 1 + packages/cli/test/native-install.test.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/cli/scripts/install-native.mjs b/packages/cli/scripts/install-native.mjs index 84eb846bcb..4db76cc24c 100644 --- a/packages/cli/scripts/install-native.mjs +++ b/packages/cli/scripts/install-native.mjs @@ -49,6 +49,7 @@ export function relocateToolchain(manifest, sourceDirectory, destinationDirector "declarations", "comptime", "wasi_node_runner", + "lib_diagnostics", ]) { if (typeof relocated[name] === "string") relocated[name] = pathFrom(relocated[name]); } diff --git a/packages/cli/test/native-install.test.ts b/packages/cli/test/native-install.test.ts index c75b542679..5e51a2447f 100644 --- a/packages/cli/test/native-install.test.ts +++ b/packages/cli/test/native-install.test.ts @@ -53,6 +53,7 @@ function fixture() { dsymutil: "dsymutil", comptime: "../lib/comptime", wasi_node_runner: "../lib/wasi/cli/wasi-runner.js", + lib_diagnostics: "../lib/lib-diagnostics.json", }; writeFileSync(binary + ".json", JSON.stringify(toolchain)); const helpers = [ @@ -93,6 +94,9 @@ test("installs a direct executable and relocatable references to platform assets expect(resolve(dirname(installed), manifest.wasi_node_runner)).toBe( join(platform, "wasi/cli/wasi-runner.js"), ); + expect(resolve(dirname(installed), manifest.lib_diagnostics)).toBe( + join(platform, "lib-diagnostics.json"), + ); expect(resolve(dirname(installed), manifest.runtime_packs[0].path)).toBe( join(platform, "runtime"), ); From f591e8e6d7b5db64aa34a493d02b2d66fcc2dbcf Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:14:25 -0500 Subject: [PATCH 23/49] Print how a --profile-use build's profile matched `scriptc build --profile-use` prints one line on stderr with the number of functions the profile annotated, the number whose control flow changed since it was collected (mismatched), and the number it has no record of. When more than 10% of the functions the profile describes are mismatched, it also prints a warning to collect the profile again. The build still succeeds. Both the compiler library's build and the native compiler's build report the match. Cached builds report the match of the build that produced them: the native codegen and object caches store it beside the cached objects, and the executable cache stores it in its entry. --- docs/content/docs/cli.mdx | 21 ++++- packages/cli/test/profile.test.ts | 19 +++- .../src/backend/native-codegen.test.ts | 39 ++++++++- .../compiler/src/backend/native-codegen.ts | 69 ++++++++++++++- .../src/backend/native-profile.test.ts | 47 ++++++++++ .../compiler/src/backend/native-profile.ts | 26 ++++++ packages/compiler/src/backend/native-tools.ts | 16 +++- packages/compiler/src/cli/command.ts | 10 +++ packages/compiler/src/cli/usage.ts | 6 +- .../src/executable/executable-cache.ts | 16 ++++ packages/compiler/src/index.ts | 16 +++- packages/compiler/src/native/compiler.test.ts | 87 ++++++++++++++++++- packages/compiler/src/native/compiler.ts | 75 +++++++++++++--- 13 files changed, 416 insertions(+), 31 deletions(-) diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 5afa6ff730..0a82775ca3 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -170,7 +170,7 @@ With `--sparse`, the profile leaves out the functions the runs never reached. Th
On Linux, build an instrumented executable for profile-guided optimization. Each run writes a raw profile to the path pattern, where %p expands to the process ID; the LLVM_PROFILE_FILE environment variable overrides the pattern at run time. A relative pattern resolves from the current working directory. Instrumented programs run slower. Valid only for executable builds with --optimization=release or speed.
--profile-use <file.profdata>
-
Optimize the executable with a profile from scriptc merge-profiles: code is laid out, inlined, and split by how the profiled runs used it. Code the profile does not describe, or describes from an older version of the program, is optimized as without a profile, so a stale profile only loses part of its gain. A new or changed profile compiles every partition of the program again. Valid only for executable builds with --optimization=release or speed.
+
Optimize the executable with a profile from scriptc merge-profiles: code is laid out, inlined, and split by how the profiled runs used it. Code the profile does not describe, or describes from an older version of the program, is optimized as without a profile, so a stale profile only loses part of its gain. A new or changed profile compiles every partition of the program again. scriptc build prints how the profile matched the program's functions on stderr; see Profile-guided optimization. Valid only for executable builds with --optimization=release or speed.
--npm-static <pkg[,pkg…]|auto>
Name npm packages to compile even if they don't ship their own type declarations (repeatable). A trailing ..., as in fs-extra..., also names every package that package depends on. auto uses the default automatic selection. Packages that can't compile produce diagnostics. See npm Dependencies.
@@ -262,6 +262,25 @@ $ scriptc build app.ts --optimization=speed --profile-use app.profdata Train on workloads like the ones the program will run: code the training never reached is optimized as without a profile. The profile can be committed and reused as the program changes. Functions whose control flow changed since the profile was collected lose their profile, and the rest keep theirs, so regenerate it when much of the program has changed. [Build parallelism](/docs/how-it-works#build-parallelism) describes what a profile adds to build time. +`scriptc build --profile-use` prints one line on stderr with how the profile matched the program's functions: + +``` +scriptc: profile app.profdata: 9170 functions annotated, 0 mismatched, 28636 without a record +``` + +- **annotated:** functions optimized with the profile's counts. +- **mismatched:** functions whose control flow changed since the profile was collected. They are optimized without counts. +- **without a record:** functions the profile does not describe: new functions, and functions the profiled runs never reached. +- When the build reuses cached partitions whose counts were not recorded, the line ends with the number of those partitions. + +When more than 10% of the functions the profile describes (annotated and mismatched) are mismatched, the build also prints a warning: + +``` +scriptc: warning: 3739 of the 8947 functions app.profdata describes (41.8%) changed since it was collected and are optimized without their counts; collect the profile again +``` + +The build still succeeds. Collecting the profile again with the current program and scriptc version restores the counts. + ## Target CPU By default, x86-64 executables run on any x86-64 CPU, so the compiler uses only the baseline instruction set (SSE2). `--target-cpu` compiles the program's code for a higher x86-64 microarchitecture level. Each level includes the ones below it: diff --git a/packages/cli/test/profile.test.ts b/packages/cli/test/profile.test.ts index 24c759e921..da40a15b62 100644 --- a/packages/cli/test/profile.test.ts +++ b/packages/cli/test/profile.test.ts @@ -63,8 +63,16 @@ test.skipIf(process.platform !== "linux")( expect(merge.stdout).toBe(`${merged}\n`); const optimized = join(dir, "optimized"); - await scriptc(["build", entry, "--profile-use", merged, "-o", optimized], env); + const fresh = await scriptc(["build", entry, "--profile-use", merged, "-o", optimized], env); expect((await execFileAsync(optimized, ["1000"])).stdout).toBe("total 167499\n"); + // The build reports how the profile matched the program's functions. + expect(fresh.stderr).toMatch( + /^scriptc: profile .*: [1-9]\d* functions annotated, 0 mismatched, \d+ without a record\n$/, + ); + expect(fresh.stderr).toContain(`scriptc: profile ${merged}: `); + // A build from the cache reports the same match. + const again = await scriptc(["build", entry, "--profile-use", merged, "-o", optimized], env); + expect(again.stderr).toBe(fresh.stderr); // A sparse profile leaves out the runtime functions the runs never // reached. @@ -77,10 +85,15 @@ test.skipIf(process.platform !== "linux")( await scriptc(["build", entry, "--profile-use", sparse, "-o", optimized], env); expect((await execFileAsync(optimized, ["1000"])).stdout).toBe("total 167499\n"); - // A profile from an older version of the program still builds it. + // A profile from an older version of the program still builds it. The + // edited function no longer matches its counts, and the build warns. await writeFile(entry, PROGRAM + 'console.log("edited");\n'); - await scriptc(["build", entry, "--profile-use", merged, "-o", optimized], env); + const stale = await scriptc(["build", entry, "--profile-use", merged, "-o", optimized], env); expect((await execFileAsync(optimized, ["1000"])).stdout).toBe("total 167499\nedited\n"); + expect(stale.stderr).toMatch(/ [1-9]\d* mismatched, /); + expect(stale.stderr).toMatch( + /\nscriptc: warning: [1-9]\d* of the \d+ functions .* describes \([\d.]+%\) changed since it was collected and are optimized without their counts; collect the profile again\n$/, + ); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/packages/compiler/src/backend/native-codegen.test.ts b/packages/compiler/src/backend/native-codegen.test.ts index 5463b868ea..64961bcaa1 100644 --- a/packages/compiler/src/backend/native-codegen.test.ts +++ b/packages/compiler/src/backend/native-codegen.test.ts @@ -1,4 +1,4 @@ -import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"; +import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { afterEach, expect, test } from "vitest"; @@ -26,6 +26,7 @@ async function fakePackage( emptyOutput?: boolean; missingOutput?: boolean; changePackageDuringEmit?: boolean; + profileReport?: string; } = {}, ) { const root = await mkdtemp(join(tmpdir(), "scriptc-native-helper-test-")); @@ -72,6 +73,7 @@ ${ ? ":" : 'for each in $outputs; do cp "$input" "$each"; done' } +${options.profileReport === undefined ? "" : `printf '%s\\n' '${options.profileReport}'`} `, ); await chmod(bin, 0o755); @@ -196,6 +198,41 @@ test("profiles reach the helper and key the native cache by their bytes", async expect(calls[2]).toContain("--profile-use /profiles/a.profdata"); }); +test("a cached artifact keeps the profile match of the build that produced it", async () => { + const pkg = await fakePackage({ + profileReport: + '{"ok":true,"profile":{"annotated":120,"mismatched":7,"missing":40,"unknown_partitions":0}}', + }); + const used = { + ...request(pkg.root, pkg.packageJson, join(pkg.root, "used.o")), + profile: { use: { path: "/profiles/a.profdata", digest: "a".repeat(64) } }, + }; + const match = { annotated: 120, mismatched: 7, missing: 40, unknownPartitions: 0 }; + expect((await emitNativeArtifact(used)).profileMatch).toEqual(match); + const again = await emitNativeArtifact({ ...used, outputPath: join(pkg.root, "again.o") }); + expect(again.profileMatch).toEqual(match); + expect((await readFile(pkg.log, "utf8")).trim().split("\n")).toHaveLength(1); + // Without the cached match, the cached partition's counts are unknown. + const cache = join(pkg.root, "cache", "native-codegen-v1"); + const [shard] = await readdir(cache); + for (const name of await readdir(join(cache, shard!))) + if (name.includes(".profile-match")) await rm(join(cache, shard!, name)); + const unknown = await emitNativeArtifact({ ...used, outputPath: join(pkg.root, "unknown.o") }); + expect(unknown.profileMatch).toEqual({ + annotated: 0, + mismatched: 0, + missing: 0, + unknownPartitions: 1, + }); + // Builds without a profile carry no match, from the helper or the cache. + const plain = request(pkg.root, pkg.packageJson, join(pkg.root, "plain.o")); + expect((await emitNativeArtifact(plain)).profileMatch).toBeUndefined(); + expect( + (await emitNativeArtifact({ ...plain, outputPath: join(pkg.root, "plain-again.o") })) + .profileMatch, + ).toBeUndefined(); +}); + test("chunked LLVM input preserves emitted bytes and the native cache identity", async () => { const pkg = await fakePackage(); const first = request(pkg.root, pkg.packageJson, join(pkg.root, "chunks.o")); diff --git a/packages/compiler/src/backend/native-codegen.ts b/packages/compiler/src/backend/native-codegen.ts index 413fb7e02f..84c76ce91b 100644 --- a/packages/compiler/src/backend/native-codegen.ts +++ b/packages/compiler/src/backend/native-codegen.ts @@ -2,7 +2,17 @@ import { execFile } from "node:child_process"; import { createHash, type Hash } from "node:crypto"; import { closeSync, constants, openSync, readSync } from "node:fs"; import { createRequire } from "node:module"; -import { access, chmod, copyFile, mkdir, readFile, rename, rm, stat } from "node:fs/promises"; +import { + access, + chmod, + copyFile, + mkdir, + readFile, + rename, + rm, + stat, + writeFile, +} from "node:fs/promises"; import { dirname, join } from "node:path"; import { promisify } from "node:util"; import { @@ -436,7 +446,10 @@ export async function emitNativeArtifact( dependencies: helper.dependencies, outputPaths: outputs, } satisfies NativeCodegenArtifact; - if (await installCachedPartitions(cached, outputs)) return artifact; + if (await installCachedPartitions(cached, outputs)) { + if (options.profile?.use === undefined) return artifact; + return { ...artifact, profileMatch: await cachedProfileMatch(cached[0]!, outputs.length) }; + } // Partitioned programs also reuse individual partition artifacts, so an // edit recompiles only the partitions it affects. The directory names the // exact helper build and target; the helper keys entries by content. @@ -459,6 +472,7 @@ export async function emitNativeArtifact( // The helper reads a private copy: the caller's .ll may be shared with a // concurrent build of another entry that has the same output stem. const input = privateSiblingPath(options.outputPath, "native-input"); + const matchStage = privateSiblingPath(options.outputPath, "native-profile-match"); try { if (isLlvmFile(options.llvm)) await copyFile(options.llvm.file, input); else await writeLlvmSource(input, options.llvm, 0o600); @@ -493,6 +507,8 @@ export async function emitNativeArtifact( } await chmod(stage, artifactMode()); } + const profileMatch = + options.profile?.use === undefined ? null : parseNativeProfileMatch(stdout); // Cache publication is an optimization boundary. The helper has already // produced a valid caller artifact, so a read-only/full cache must not // discard it or turn an otherwise successful build into an exception. @@ -500,23 +516,68 @@ export async function emitNativeArtifact( root !== null && (await nativeArtifactDependenciesStillMatch(helper.dependencies).catch(() => false)) ) { + // The match goes in first: a reader that finds every partition finds it too. + if (profileMatch !== null) { + await writeFile( + matchStage, + `${JSON.stringify({ profile: helperProfileMatch(profileMatch) })}\n`, + { + mode: 0o600, + }, + ) + .then(() => publishCachedFile(matchStage, cachedProfileMatchPath(cached[0]!))) + .catch(() => undefined); + } for (let index = 0; index < stages.length; index++) await publishCachedFile(stages[index]!, cached[index]!).catch(() => undefined); } for (let index = 0; index < stages.length; index++) await rename(stages[index]!, outputs[index]!); await pruneBuildCache(root); - const profileMatch = - options.profile?.use === undefined ? null : parseNativeProfileMatch(stdout); return profileMatch === null ? artifact : { ...artifact, profileMatch }; } finally { await Promise.all([ ...stages.map((stage) => rm(stage, { force: true }).catch(() => undefined)), rm(input, { force: true }).catch(() => undefined), + rm(matchStage, { force: true }).catch(() => undefined), ]); } } +/** The cache entry beside a cached artifact's first partition that holds the + * profile match of the build that produced it. */ +function cachedProfileMatchPath(firstPartition: string): string { + return `${firstPartition}.profile-match`; +} + +/** The match in the helper's own JSON form, as parseNativeProfileMatch reads it. */ +function helperProfileMatch(match: NativeProfileMatch): Record { + return { + annotated: match.annotated, + mismatched: match.mismatched, + missing: match.missing, + unknown_partitions: match.unknownPartitions, + }; +} + +/** The profile match of a cached artifact. Without a valid entry, every + * partition's counts are unknown. */ +async function cachedProfileMatch( + firstPartition: string, + partitions: number, +): Promise { + const path = cachedProfileMatchPath(firstPartition); + const text = (await validCachedFile(path)) ? await readFile(path, "utf8").catch(() => "") : ""; + return ( + parseNativeProfileMatch(text) ?? { + annotated: 0, + mismatched: 0, + missing: 0, + unknownPartitions: partitions, + } + ); +} + /** Install a cached artifact only when every partition is present and valid. */ async function installCachedPartitions( cached: readonly (string | null)[], diff --git a/packages/compiler/src/backend/native-profile.test.ts b/packages/compiler/src/backend/native-profile.test.ts index b2cb0c75d1..18f0c6f736 100644 --- a/packages/compiler/src/backend/native-profile.test.ts +++ b/packages/compiler/src/backend/native-profile.test.ts @@ -3,6 +3,7 @@ import { executableLinkInputs } from "./link-plan-core.js"; import { parseNativeProfileMatch, nativeProfileHelperArgs, + renderNativeProfileMatch, nativeProfileKey, nativeProfileRefusal, profileGenerateLinkerArgs, @@ -84,3 +85,49 @@ test("the helper's profile match is read from its output", () => { expect(parseNativeProfileMatch('{"ok":true}\n')).toBeNull(); expect(parseNativeProfileMatch('{"ok":true,"profile":{"annotated":-1}}\n')).toBeNull(); }); + +test("the build reports the match, and warns when the profile is stale", () => { + expect( + renderNativeProfileMatch("app.profdata", { + annotated: 9170, + mismatched: 0, + missing: 28636, + unknownPartitions: 0, + }), + ).toBe( + "scriptc: profile app.profdata: 9170 functions annotated, 0 mismatched, 28636 without a record\n", + ); + // 10% of the described functions changed: at the threshold, no warning. + expect( + renderNativeProfileMatch("app.profdata", { + annotated: 90, + mismatched: 10, + missing: 5, + unknownPartitions: 1, + }), + ).toBe( + "scriptc: profile app.profdata: 90 functions annotated, 10 mismatched, 5 without a record, 1 partition without counts\n", + ); + expect( + renderNativeProfileMatch("app.profdata", { + annotated: 5208, + mismatched: 3739, + missing: 28859, + unknownPartitions: 2, + }), + ).toBe( + "scriptc: profile app.profdata: 5208 functions annotated, 3739 mismatched, 28859 without a record, 2 partitions without counts\n" + + "scriptc: warning: 3739 of the 8947 functions app.profdata describes (41.8%) changed since it was collected and are optimized without their counts; collect the profile again\n", + ); + // Nothing described: no share to warn about. + expect( + renderNativeProfileMatch("app.profdata", { + annotated: 0, + mismatched: 0, + missing: 12, + unknownPartitions: 0, + }), + ).toBe( + "scriptc: profile app.profdata: 0 functions annotated, 0 mismatched, 12 without a record\n", + ); +}); diff --git a/packages/compiler/src/backend/native-profile.ts b/packages/compiler/src/backend/native-profile.ts index 904994a7dc..85b1fdca41 100644 --- a/packages/compiler/src/backend/native-profile.ts +++ b/packages/compiler/src/backend/native-profile.ts @@ -83,6 +83,32 @@ export function parseNativeProfileMatch(stdout: string): NativeProfileMatch | nu return null; } +/** The share of the functions a profile describes whose control flow changed + * since it was collected, above which `scriptc build` warns that the profile + * is stale. A freshly collected profile has none. */ +export const STALE_PROFILE_SHARE = 0.1; + +/** `scriptc build --profile-use`'s report on stderr: one line with the match, + * and a warning line when more than STALE_PROFILE_SHARE of the functions the + * profile describes changed since it was collected. */ +export function renderNativeProfileMatch(profile: string, match: NativeProfileMatch): string { + const unknown = + match.unknownPartitions === 0 + ? "" + : `, ${match.unknownPartitions} partition${match.unknownPartitions === 1 ? "" : "s"} without counts`; + const lines = [ + `scriptc: profile ${profile}: ${match.annotated} functions annotated, ${match.mismatched} mismatched, ${match.missing} without a record${unknown}`, + ]; + const described = match.annotated + match.mismatched; + if (described > 0 && match.mismatched / described > STALE_PROFILE_SHARE) { + const percent = ((match.mismatched / described) * 100).toFixed(1); + lines.push( + `scriptc: warning: ${match.mismatched} of the ${described} functions ${profile} describes (${percent}%) changed since it was collected and are optimized without their counts; collect the profile again`, + ); + } + return `${lines.join("\n")}\n`; +} + /** Cache-key part; empty without a profile so other keys are unchanged. */ export function nativeProfileKey(profile: NativeProfileOptions | undefined): string { if (profile?.generate !== undefined) return `profile-generate:${profile.generate}`; diff --git a/packages/compiler/src/backend/native-tools.ts b/packages/compiler/src/backend/native-tools.ts index 2f1b88ed05..9d6f2cf944 100644 --- a/packages/compiler/src/backend/native-tools.ts +++ b/packages/compiler/src/backend/native-tools.ts @@ -8,7 +8,12 @@ import { validateNativeCodegenVersion, type NativeCodegenOutputKind, } from "./native-codegen-core.js"; -import { nativeProfileHelperArgs, type NativeProfileOptions } from "./native-profile.js"; +import { + nativeProfileHelperArgs, + parseNativeProfileMatch, + type NativeProfileMatch, + type NativeProfileOptions, +} from "./native-profile.js"; export function runNativeTool( executable: string, @@ -48,10 +53,10 @@ export function emitNativeObject(options: { partitionCache?: string; /** Profile-guided optimization: instrumentation or an indexed profile. */ profile?: NativeProfileOptions; -}): string[] { +}): { outputs: string[]; profileMatch: NativeProfileMatch | null } { verifyNativeHelper(options); const outputs = nativePartitionPaths(options.outputPath, options.partitions ?? 1); - runNativeTool(options.executable, [ + const stdout = runNativeTool(options.executable, [ "emit", "--input", options.inputPath, @@ -74,7 +79,10 @@ export function emitNativeObject(options: { ...nativeProfileHelperArgs(options.profile), ]); for (const output of outputs) requireNativeArtifact(output); - return outputs; + return { + outputs, + profileMatch: options.profile?.use === undefined ? null : parseNativeProfileMatch(stdout), + }; } /** Merges raw or indexed IR profiles into one indexed profile. */ diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 58c462e33b..4c3ce111ff 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -28,6 +28,7 @@ import { import { isTypePatchCode } from "../diagnostics/diagnostic.js"; import { isPerformanceCode, renderPerformanceWarnings } from "../diagnostics/dynamic-tier.js"; import { isNativeOptimization } from "../backend/optimization.js"; +import { renderNativeProfileMatch } from "../backend/native-profile.js"; import { hostTargetCpu, isTargetCpuOption, @@ -712,6 +713,15 @@ async function main(args: string[], host: CliHost): Promise { } } if (result.artifact.kind === "exe") { + // How the profile matched the program's functions; `run` keeps stderr + // for the program's own output. + const profileUse = values["profile-use"]; + if ( + command === "build" && + profileUse !== undefined && + result.artifact.profileMatch !== undefined + ) + process.stderr.write(renderNativeProfileMatch(profileUse, result.artifact.profileMatch)); if (!values["keep-llvm"]) rmSync(result.artifact.translationUnitPath, { force: true }); } else if (result.artifact.kind === "obj") { nativeLinkInfo = result.artifact.nativeLinkInfo; diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index c957dc6296..9b98bd11d4 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -86,8 +86,10 @@ Options: code is laid out, inlined and split by how the profiled runs used it. Code the profile does not describe, or describes from an older version of the program, is - optimized as without one (release or speed; --emit=exe - only) + optimized as without one. build prints how the profile + matched on stderr, and warns when more than 10% of the + functions it describes changed (release or speed; + --emit=exe only) --keep-llvm keep generated LLVM IR beside the executable (default) --no-keep-llvm delete generated LLVM IR after compiling --emit-ir also write IR beside an executable or library archive; diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 2bba26cb23..d557d6e021 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -12,6 +12,7 @@ import { } from "node:fs/promises"; import { basename, dirname, join, resolve } from "node:path"; import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { NativeProfileMatch } from "../backend/native-profile.js"; import type { FrontendInputSnapshot } from "../frontend/input-tracker.js"; import { frontendInputsStillMatch, validFrontendInputSnapshot } from "../frontend/input-tracker.js"; import { compilerReleaseVersion } from "../library/sidecar.js"; @@ -129,6 +130,8 @@ interface EarlyExecutableCacheStamp { native: EarlyExecutableNativeFeatures; /** The build's performance warnings (SC61xx), which a cache hit repeats. */ performance?: ScrDiagnostic[]; + /** How a --profile-use build's profile matched, which a cache hit reports. */ + profileMatch?: NativeProfileMatch; integrity: string; } @@ -213,12 +216,23 @@ export interface EarlyExecutableCacheHit { frontend: FrontendInputSnapshot; /** The performance warnings the build reported (SC61xx), repeated on a hit. */ performance?: ScrDiagnostic[]; + /** How a --profile-use build's profile matched, reported again on a hit. */ + profileMatch?: NativeProfileMatch; } export interface EarlyExecutableCachePublish extends EarlyExecutableCacheHit { nativeDependencies?: NativeArtifactDependency[]; } +function isProfileMatch(value: unknown): value is NativeProfileMatch { + if (typeof value !== "object" || value === null) return false; + const match = value as Record; + return ["annotated", "mismatched", "missing", "unknownPartitions"].every((key) => { + const count = match[key]; + return typeof count === "number" && Number.isInteger(count) && count >= 0; + }); +} + const BOOLEAN_NATIVE_KEYS = [ "regex", "copying", @@ -671,6 +685,7 @@ async function readExecutableCache( ...(Array.isArray(stamp.performance) && stamp.performance.length > 0 ? { performance: stamp.performance } : {}), + ...(isProfileMatch(stamp.profileMatch) ? { profileMatch: stamp.profileMatch } : {}), ...(ir === null ? {} : { irPath: paths.irPath }), ...(llvmSourcePath === undefined ? {} : { llvmSourcePath }), ...(irSourcePath === undefined ? {} : { irSourcePath }), @@ -861,6 +876,7 @@ export async function publishEarlyExecutableCache( ...(result.performance === undefined || result.performance.length === 0 ? {} : { performance: result.performance }), + ...(result.profileMatch === undefined ? {} : { profileMatch: result.profileMatch }), }; const stamp: EarlyExecutableCacheStamp = { ...unsigned, integrity: stampIntegrity(unsigned) }; await writeFile(join(stage, "stamp.json"), `${JSON.stringify(stamp)}\n`, { mode: 0o600 }); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 9d73d8209d..a7f4b34010 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -529,6 +529,14 @@ function performanceWarnings(sink: CompileWarningSink | undefined): { return performance.length === 0 ? {} : { performance }; } +/** The profile match of a --profile-use build's program object, for its + * artifact and its executable cache entry, which a cache hit reports again. */ +function profileMatchField(object: NativeProgramObject | null): { + profileMatch?: NativeProfileMatch; +} { + return object?.profileMatch === undefined ? {} : { profileMatch: object.profileMatch }; +} + function withCompileWarnings( result: T, sink: CompileWarningSink, @@ -1675,6 +1683,7 @@ async function compileTracked( path: opts.outPath, translationUnitPath: earlyHit.llvmPath, backend: earlyHit.native.backend, + ...(earlyHit.profileMatch === undefined ? {} : { profileMatch: earlyHit.profileMatch }), }, binaryPath: opts.outPath, llvmPath: earlyHit.llvmPath, @@ -1752,6 +1761,7 @@ async function compileTracked( executableRestored: true, nativeDependencies: dependencies, frontend: earlyHit.frontend, + ...profileMatchField(nativeProgramObject), }); }, nativeProgramObject?.partitionPaths, @@ -1797,6 +1807,7 @@ async function compileTracked( path: opts.outPath, translationUnitPath: earlyHit.llvmPath, backend: earlyHit.native.backend, + ...profileMatchField(nativeProgramObject), }, binaryPath: opts.outPath, llvmPath: earlyHit.llvmPath, @@ -1888,6 +1899,7 @@ async function compileTracked( frontend: frontendInputs.snapshot(), ...(irSourcePath === undefined ? {} : { irPath: irSourcePath }), ...performanceWarnings(warningSink), + ...profileMatchField(nativeProgramObject), }); publishedExecutable = true; }, @@ -1907,6 +1919,7 @@ async function compileTracked( frontend: frontendInputs.snapshot(), ...(irSourcePath === undefined ? {} : { irPath: irSourcePath }), ...performanceWarnings(warningSink), + ...profileMatchField(nativeProgramObject), }).catch(() => undefined); } } catch (err) { @@ -1935,7 +1948,6 @@ async function compileTracked( } await pruneBuildCache(cacheRoot); timing("complete"); - const profileMatch = nativeProgramObject?.profileMatch; return { ok: true, artifact: { @@ -1943,7 +1955,7 @@ async function compileTracked( path: opts.outPath, translationUnitPath: llvmPath, backend, - ...(profileMatch === undefined ? {} : { profileMatch }), + ...profileMatchField(nativeProgramObject), }, binaryPath: opts.outPath, llvmPath, diff --git a/packages/compiler/src/native/compiler.test.ts b/packages/compiler/src/native/compiler.test.ts index a085356a09..c9636ea76d 100644 --- a/packages/compiler/src/native/compiler.test.ts +++ b/packages/compiler/src/native/compiler.test.ts @@ -107,7 +107,7 @@ test.each([ }); vi.mocked(emitNativeObject).mockImplementation((options) => { writeFileSync(options.outputPath, "object"); - return [options.outputPath]; + return { outputs: [options.outputPath], profileMatch: null }; }); const cache = new NativeCache(join(directory, "cache")); const key = contentDigest("executable link"); @@ -250,7 +250,7 @@ test.each([false, true])( expect(emitted).toContain("define i32 @main"); if (fail) throw new Error("codegen failed"); writeFileSync(options.outputPath, "new object"); - return [options.outputPath]; + return { outputs: [options.outputPath], profileMatch: null }; }); const target = MACOS_ARM64_TARGET; const toolchain: NativeToolchain = { @@ -341,7 +341,7 @@ test.skipIf(process.platform === "win32").each([false, true])( }); vi.mocked(emitNativeObject).mockImplementation((options) => { writeFileSync(options.outputPath, "object"); - return [options.outputPath]; + return { outputs: [options.outputPath], profileMatch: null }; }); vi.mocked(openNativeExecutableCache).mockReturnValue(null); const links: string[][] = []; @@ -378,3 +378,84 @@ test.skipIf(process.platform === "win32").each([false, true])( if (lldFails) expect(links[1]).not.toContain("-fuse-ld=lld"); }, ); + +test.skipIf(process.platform === "win32")( + "a --profile-use executable reports the helper's profile match", + () => { + const directory = mkdtempSync(join("/tmp", "scriptc-native-profile-")); + directories.push(directory); + const clang = join(directory, "clang"); + writeFileSync(clang, "#!/bin/sh\n"); + chmodSync(clang, 0o755); + const entry = join(directory, "entry.ts"); + const output = join(directory, "program"); + const profile = join(directory, "app.profdata"); + writeFileSync(profile, "indexed profile"); + const module: IrModule = { + irVersion: 17, + sourceFile: entry, + entry: "%main", + functions: [ + { + name: "%main", + params: [], + locals: [], + body: [], + returnType: VOID, + loc: { file: entry, start: 0, end: 0 }, + }, + ], + }; + vi.mocked(runNativeFrontend).mockReturnValue({ + preflight: [], + entryText: () => "", + entryExports: () => new Map(), + entryContract: () => { + throw new Error("unexpected contract request"); + }, + sourceTexts: () => new Map(), + npmStatic: [], + npmImportSites: new Map(), + dispose: () => {}, + lower: () => ({ + module, + diagnostics: [], + runtimeFences: [], + stats: { statementsTotal: 0, statementsFailed: 0, functionsSkipped: 0 }, + }), + }); + const match = { annotated: 120, mismatched: 7, missing: 40, unknownPartitions: 0 }; + vi.mocked(emitNativeObject).mockImplementation((options) => { + expect(options.profile?.use?.path).toBe(profile); + writeFileSync(options.outputPath, "object"); + return { outputs: [options.outputPath], profileMatch: match }; + }); + vi.mocked(openNativeExecutableCache).mockReturnValue(null); + vi.mocked(execFileSync).mockImplementation((_command, args) => { + const list = args as string[]; + writeFileSync(list[list.indexOf("-o") + 1]!, "executable"); + return ""; + }); + const target = LINUX_X64_GNU_TARGET; + const compiler = new NativeCompiler({ + compilerVersion: "0.0.0", + target, + helper: target.helper, + ts7Executable: "unused", + helperExecutable: "unused", + helperPackageRoot: "unused", + runtimePackRoot: "unused", + linker: clang, + linkerArgs: [], + dsymutil: "unused", + }); + const result = compiler.compile(entry, { + outDir: directory, + outPath: output, + backend: "llvm", + optimization: "speed", + profileUse: profile, + }); + expect(result).toMatchObject({ ok: true, artifact: { kind: "exe", profileMatch: match } }); + }, +); diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index f2734d2ee0..ea3772db3c 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -63,6 +63,8 @@ import { contentDigest, fileDigest, partsDigest, type NativeCache } from "./cach import { nativeProfileKey, nativeProfileRefusal, + parseNativeProfileMatch, + type NativeProfileMatch, type NativeProfileOptions, } from "../backend/native-profile.js"; import { splitLlvmLibraryProgram } from "../backend/llvm/split.js"; @@ -78,6 +80,18 @@ function partitionCacheKey(key: string, index: number): string { return index === 0 ? key : contentDigest(`${key}:${index}`); } +/** The object cache entry that holds a cached program's profile match. */ +function profileMatchCacheKey(key: string): string { + return contentDigest(`${key}:profile-match`); +} + +/** A program's native objects, and how a --profile-use build's profile + * matched its functions. */ +interface NativeObjects { + paths: string[]; + profileMatch?: NativeProfileMatch; +} + function failure(error: unknown, entry: string, sources: Map): CompileFailure { return { ok: false, @@ -186,7 +200,7 @@ export class NativeCompiler { partitions = 1, profile?: NativeProfileOptions, llvmDigest: () => string = () => fileDigest(input), - ): string[] { + ): NativeObjects { const toolchain = this.toolchain; const outputs = nativePartitionPaths(output, partitions); const helperOptions = { @@ -223,7 +237,20 @@ export class NativeCompiler { verifyNativeHelper(helperOptions); if (nativeFileIdentity(toolchain.helperExecutable) === identity) { cached.forEach((bytes, index) => writeFileSync(outputs[index]!, bytes!)); - return outputs; + if (profile?.use === undefined) return { paths: outputs }; + // Without the cached match, every partition's counts are unknown. + const match = this.cache.read("object", profileMatchCacheKey(key)); + return { + paths: outputs, + profileMatch: (match === null + ? null + : parseNativeProfileMatch(match.toString("utf8"))) ?? { + annotated: 0, + mismatched: 0, + missing: 0, + unknownPartitions: outputs.length, + }, + }; } key = null; } @@ -235,7 +262,7 @@ export class NativeCompiler { this.cache === null || identity === null || partitions < 2 ? null : this.cache.partitionDirectory(identity); - emitNativeObject({ + const { profileMatch } = emitNativeObject({ executable: toolchain.helperExecutable, packageRoot: toolchain.helperPackageRoot, compilerVersion: toolchain.compilerVersion, @@ -252,15 +279,30 @@ export class NativeCompiler { }); if (this.cache !== null && key !== null) { try { - if (nativeFileIdentity(toolchain.helperExecutable) === identity) + if (nativeFileIdentity(toolchain.helperExecutable) === identity) { + // The match goes in first: a reader that finds every partition finds it too. + if (profileMatch !== null) + this.cache.write( + "object", + profileMatchCacheKey(key), + `${JSON.stringify({ + profile: { + annotated: profileMatch.annotated, + mismatched: profileMatch.mismatched, + missing: profileMatch.missing, + unknown_partitions: profileMatch.unknownPartitions, + }, + })}\n`, + ); outputs.forEach((path, index) => this.cache!.write("object", partitionCacheKey(key!, index), readFileSync(path)), ); + } } catch { /* An updated helper or unavailable cache only prevents reuse. */ } } - return outputs; + return profileMatch === null ? { paths: outputs } : { paths: outputs, profileMatch }; } private emitProgramObject( @@ -272,7 +314,7 @@ export class NativeCompiler { library: boolean, profile?: NativeProfileOptions, llvmDigest?: () => string, - ): string[] { + ): NativeObjects { // Library archives merge dev shards relocatably; executables link the // helper's program partitions directly. const llvm = @@ -303,7 +345,7 @@ export class NativeCompiler { } const merged = localizeNativeLibrary(this.toolchain, stage, objects, [], split.publicSymbols); renameSync(merged, output); - return [output]; + return { paths: [output] }; } compile(entry: string, options: CompileRequestOptions): CompileRequestResult { @@ -427,6 +469,9 @@ export class NativeCompiler { const kept = executableCache?.keep(output) ?? false; const restored = kept || (executableCache?.restore(stagedOutput) ?? false); timing(restored ? "executable-cache-hit" : "executable-cache-miss"); + // The executable cache leaves out --profile-use builds, so this is set + // whenever the build has a profile to report. + let profileMatch: NativeProfileMatch | undefined; if (!restored) { // A missing or invalid runtime pack fails before code generation. if (outputKind === "exe") selectPack(); @@ -440,8 +485,8 @@ export class NativeCompiler { object, ]); requireNativeArtifact(object); - } else if (outputKind === "exe") - programObjects = this.emitProgramObject( + } else if (outputKind === "exe") { + const objects = this.emitProgramObject( llvmInput, object, entry, @@ -451,7 +496,9 @@ export class NativeCompiler { profile, digestLlvm, ); - else + programObjects = objects.paths; + profileMatch = objects.profileMatch; + } else this.emitObject( llvmInput, object, @@ -578,7 +625,13 @@ export class NativeCompiler { rmSync(output + ".dSYM", { recursive: true, force: true }); return { ok: true, - artifact: { kind: "exe", path: output, translationUnitPath: llvmPath, backend: "llvm" }, + artifact: { + kind: "exe", + path: output, + translationUnitPath: llvmPath, + backend: "llvm", + ...(profileMatch === undefined ? {} : { profileMatch }), + }, binaryPath: output, llvmPath, backend: "llvm", From 6f373b983b4e4b26a144078b57b4be59b13aeaec Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:26:16 -0500 Subject: [PATCH 24/49] Keep every field of a record converted into a union arm A record that converts into several arms of a union by a width conversion preferred the unique arm whose fields the record has or that are undefined-only, even when that arm holds the record only by dropping fields and another arm keeps every field but declares an optional field the record lacks (`adapts?: true`). The record was wrapped as the dropping arm: - In a discriminated union the value kept its discriminant but had the other arm's tag, so narrowing on the discriminant stopped the program with "a narrowed value does not have the type its narrowing proved". - Without a discriminant the dropped fields were lost: `"fnName" in value` was false and JSON.stringify left them out. The native compiler hit the first case on tests/corpus/2910-overloaded-callable-alias.ts: the closure that a value read of an overloaded function alias builds is a record that is not typed by IrExpr, and it converted into the `selfRef` arm. The undefined-only preference no longer selects an arm that drops fields when another candidate keeps every field, so a discriminated union selects the arm by the record's discriminant. The lossless-arm preference still prefers the unique arm with exactly the record's fields; when there is none, it accepts the unique arm that keeps every field and declares optional fields the record lacks, which complete as absent. --- .../lowering/coercions/structural-plans.ts | 53 +++++++++++++----- .../union-record-kept-fields-arm.ts.json | 6 ++ ...record-kept-fields-no-discriminant.ts.json | 6 ++ tests/corpus/union-record-kept-fields-arm.ts | 56 +++++++++++++++++++ ...nion-record-kept-fields-no-discriminant.ts | 39 +++++++++++++ .../union-record-kept-fields-arm.ts.json | 1 + ...record-kept-fields-no-discriminant.ts.json | 1 + 7 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-arm.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-no-discriminant.ts.json create mode 100644 tests/corpus/union-record-kept-fields-arm.ts create mode 100644 tests/corpus/union-record-kept-fields-no-discriminant.ts create mode 100644 tests/harness/corpus-costs/union-record-kept-fields-arm.ts.json create mode 100644 tests/harness/corpus-costs/union-record-kept-fields-no-discriminant.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts index bff419d489..2a394c4d18 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts @@ -1,6 +1,6 @@ import { planRecordUnionLift, planRecordUnionWrap } from "../union-retag.js"; import type { WidthLift } from "../width-lift.js"; -import type { IrType } from "../../../ir/ir.js"; +import type { IrRecordShape, IrType } from "../../../ir/ir.js"; import { canDynCheckTo, isUnitType, typeEquals, UNDEFINED_T } from "../../../ir/ir.js"; import { type ClassInfo, findMethodOn, findGenericMethodOn } from "../lower-classes.js"; import type { Lowerer } from "../lowerer.js"; @@ -104,7 +104,15 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width }) ); }); - if (absentOnly.length === 1) selected = absentOnly; + // That layout does not win when it holds the record only by dropping + // fields and another arm keeps every field (`{ kind; type }` beside + // `{ kind; name; adapts?: true; type }`): the value is not its variant. + const dropsFields = (arm: IrType): boolean => !keepsEveryField(lowerer, source, arm); + if ( + absentOnly.length === 1 && + !(dropsFields(absentOnly[0]!.arm) && selected.some(({ arm }) => !dropsFields(arm))) + ) + selected = absentOnly; } // A record that fits several arms of a discriminated union selects one // by its discriminant at run time. Without this, an arm that a nested @@ -131,21 +139,19 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width // A record whose fields some arm keeps EVERY one of, each lifting // (`{ id; limit; opts?: A | B }` into `{ id; limit; opts?: A | B | C } | // { id; opts?: C }`): that arm is the value's own layout, widened; the - // others hold it only by dropping fields. Prefer the unique lossless arm. + // others hold it only by dropping fields. Prefer the unique lossless arm + // with the record's own fields, then the unique one that also declares + // optional fields the record lacks, which complete as absent. if (selected.length > 1 && src.kind === "record") { const source = lowerer.shapes.get(src.shapeId); - const lossless = selected.filter(({ arm }) => { - const target = arm.kind === "record" ? lowerer.shapes.get(arm.shapeId) : undefined; - return ( - source !== undefined && - target !== undefined && - !source.tuple && - !target.tuple && - target.fields.length === source.fields.length && - source.fields.every((field) => target.fields.some((kept) => kept.name === field.name)) - ); - }); - if (lossless.length === 1) selected = lossless; + const lossless = selected.filter(({ arm }) => keepsEveryField(lowerer, source, arm)); + const exact = lossless.filter( + ({ arm }) => + arm.kind === "record" && + lowerer.shapes.get(arm.shapeId)?.fields.length === source?.fields.length, + ); + if (exact.length === 1) selected = exact; + else if (exact.length === 0 && lossless.length === 1) selected = lossless; } if (selected.length !== 1) return null; return { how: "liftWrap", tag: selected[0]!.tag, arm: selected[0]!.arm }; @@ -241,6 +247,23 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width return null; } +/** True when the record arm `arm` has a field of every name the record + * `source` has. Tuples keep their own positional layouts. */ +function keepsEveryField( + lowerer: Lowerer, + source: IrRecordShape | undefined, + arm: IrType, +): boolean { + const target = arm.kind === "record" ? lowerer.shapes.get(arm.shapeId) : undefined; + return ( + source !== undefined && + target !== undefined && + !source.tuple && + !target.tuple && + source.fields.every((field) => target.fields.some((kept) => kept.name === field.name)) + ); +} + /** Two record arms a source record fits, where one arm declares a field * the other lacks and the source types that field `T | undefined` * (`{ count, errors: errs.length ? errs : undefined }` into `{ count; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-arm.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-arm.ts.json new file mode 100644 index 0000000000..a29a9d99a4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-arm.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-record-kept-fields-arm.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-no-discriminant.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-no-discriminant.ts.json new file mode 100644 index 0000000000..f40acc9978 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-kept-fields-no-discriminant.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-record-kept-fields-no-discriminant.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-record-kept-fields-arm.ts b/tests/corpus/union-record-kept-fields-arm.ts new file mode 100644 index 0000000000..69887cf49e --- /dev/null +++ b/tests/corpus/union-record-kept-fields-arm.ts @@ -0,0 +1,56 @@ +// A record flowing into a discriminated union where one record arm holds it +// by dropping fields and another keeps every field but declares an optional +// field the record lacks. The record's discriminant selects the arm that keeps +// its fields. + +type Loc = { file: string; start: number }; + +type Expr = + | { kind: "ref"; type: number; loc: Loc } + | { + kind: "closure"; + fnName: string; + captures: string[]; + adapts?: true; + type: number; + loc: Loc; + } + | { kind: "call"; callee: string; args: Expr[]; type: number; loc: Loc }; + +function closureOf(name: string, loc: Loc): Expr { + const closure = { + kind: "closure" as const, + fnName: name, + captures: [], + type: 1, + loc, + }; + return closure; +} + +function describe(expr: Expr): string { + if (expr.kind === "closure") + return `closure ${expr.fnName}/${expr.captures.length} ${expr.adapts ?? "-"}`; + if (expr.kind === "call") + return `call ${expr.callee}(${expr.args.map(describe).join(", ")})`; + return `ref ${expr.type}`; +} + +const loc: Loc = { file: "a.ts", start: 3 }; +const first = closureOf("pick", loc); +console.log(first.kind, describe(first), JSON.stringify(first)); + +const exprs: Expr[] = [first]; +const captured = { + kind: "closure" as const, + fnName: "inner", + captures: ["x"], + type: 2, + loc, +}; +exprs.push(captured); +exprs.push({ kind: "ref", type: 7, loc }); +console.log( + describe({ kind: "call", callee: "run", args: exprs, type: 0, loc }), +); +console.log(exprs.map((e) => e.kind).join(",")); diff --git a/tests/corpus/union-record-kept-fields-no-discriminant.ts b/tests/corpus/union-record-kept-fields-no-discriminant.ts new file mode 100644 index 0000000000..45370f152b --- /dev/null +++ b/tests/corpus/union-record-kept-fields-no-discriminant.ts @@ -0,0 +1,39 @@ +// A record flowing into a union without a discriminant where one record arm +// holds it by dropping fields and another keeps every field but declares an +// optional field the record lacks. The record converts into the arm that +// keeps its fields, and the optional field stays absent. + +type Loc = { file: string; start: number }; + +type Slot = + | { type: number; loc: Loc } + | { + fnName: string; + captures: string[]; + adapts?: true; + type: number; + loc: Loc; + }; + +const loc: Loc = { file: "a.ts", start: 3 }; + +function slotOf(name: string): Slot { + const slot = { fnName: name, captures: [], type: 1, loc }; + return slot; +} + +const slot = slotOf("stored"); +console.log("fnName" in slot, "adapts" in slot, JSON.stringify(slot)); +if ("fnName" in slot) + console.log(slot.fnName, slot.adapts, slot.captures.length); + +function describe(value: Slot): string { + return "fnName" in value + ? `${value.fnName}/${value.captures.length}` + : `type ${value.type}`; +} + +const inner = { fnName: "inner", captures: ["x"], type: 2, loc }; +const slots: Slot[] = [slot, { type: 7, loc }]; +slots.push(inner); +console.log(slots.map(describe).join(", "), JSON.stringify(slots[2])); diff --git a/tests/harness/corpus-costs/union-record-kept-fields-arm.ts.json b/tests/harness/corpus-costs/union-record-kept-fields-arm.ts.json new file mode 100644 index 0000000000..3c750a2dc5 --- /dev/null +++ b/tests/harness/corpus-costs/union-record-kept-fields-arm.ts.json @@ -0,0 +1 @@ +{ "plain": 4793, "san": 7135 } diff --git a/tests/harness/corpus-costs/union-record-kept-fields-no-discriminant.ts.json b/tests/harness/corpus-costs/union-record-kept-fields-no-discriminant.ts.json new file mode 100644 index 0000000000..5e891c40e5 --- /dev/null +++ b/tests/harness/corpus-costs/union-record-kept-fields-no-discriminant.ts.json @@ -0,0 +1 @@ +{ "plain": 4466, "san": 5253 } From 5b672087c68ce3ae69a8429240052760782c78c3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:36:11 -0500 Subject: [PATCH 25/49] Return the freed heap pages to the system after the frontend is disposed The native compiler's frontend-dispose sweep frees about 450 MB on a cold tsc-ts build, but the size-class allocator keeps the freed blocks on their classes' free lists, which only later blocks of the same size reuse. Code generation allocates other sizes, so its pages stacked on top of the freed heap and set the build's peak resident memory. - scr_heap_trim (scr_alloc.c) returns the pages of the calling thread's size-class slices that hold only free blocks (MADV_DONTNEED) and the system allocator's free pages (malloc_trim). In the single-threaded allocator the blocks of a returned page leave the free list and the class carves past its frontier instead; the remaining free blocks are relinked in address order. Worker runtimes return the calling thread's arena pages as a parking thread does. - scr_collect_cycles_phase, the full sweep after a phase's data died, calls it; the native compiler calls that sweep when it disposes the frontend. - test_heap_trim.c checks that the pages go back, that the blocks still allocated keep their contents, and that new blocks overlap none of them, with and without worker arenas (another thread allocating and freeing remotely throughout). Cold tsc-ts build, 12 interleaved rounds: peak RSS 1407 -> 1247 MB (-160 MB, -11.4%, every round); wall 87.1 -> 85.9 s (median). --- packages/compiler/native/host.c | 4 +- packages/runtime/src/scr_alloc.c | 93 ++++++++++++++++ packages/runtime/src/scr_cycle.c | 5 +- packages/runtime/src/scr_rc.h | 4 + packages/runtime/test/heap-trim.test.ts | 43 ++++++++ packages/runtime/test/test_heap_trim.c | 140 ++++++++++++++++++++++++ 6 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 packages/runtime/test/heap-trim.test.ts create mode 100644 packages/runtime/test/test_heap_trim.c diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index 24d330cc0e..b3e876d99c 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -56,7 +56,9 @@ void scr_collect_cycles_phase(void); * frontend's syntax trees and program state refer to each other, so * reference counting frees almost none of them when the frontend is * disposed, and the collector's schedule alone may not run a full pass - * before the build ends. */ + * before the build ends. The sweep then returns the freed pages to the + * system (scr_heap_trim), so code generation does not grow on top of the + * frontend's heap. */ void scriptc_native_collect_cycles(void) { scr_collect_cycles_phase(); } diff --git a/packages/runtime/src/scr_alloc.c b/packages/runtime/src/scr_alloc.c index a27df378ac..087c926f77 100644 --- a/packages/runtime/src/scr_alloc.c +++ b/packages/runtime/src/scr_alloc.c @@ -2116,6 +2116,99 @@ void scr_heap_stats(double *total, double *used) { *used = (double)(held - (idle < held ? idle : held) + sys_used); } +/* scr_heap_trim: returns the heap pages that hold only freed memory to the + * system after a phase's data died (scr_collect_cycles_phase). The + * size-class slices keep freed blocks on their class's free list, which only + * later blocks of the same class reuse, so a program whose next phase + * allocates other sizes would otherwise grow its resident memory on top of + * the freed heap. */ +#if defined(__GLIBC__) && !defined(SCR_HEAP_SANITIZER) +#include +#endif +#if SCR_SMALL_ALLOC && !defined(SCR_WORKERS) && defined(__linux__) && defined(MADV_DONTNEED) +#include +#include + +/* Returns the pages of class c's slice below its frontier that hold only + * free blocks (MADV_DONTNEED), and relinks the class's other free blocks in + * address order. A block wholly inside a returned page leaves the free list: + * the class carves new blocks past its frontier instead, and the slice's + * reservation is far larger than what a program fills. */ +static void scr_sa_return_free_pages(unsigned c, size_t page) { + size_t sz = scr_sa_class_size(c); + char *start = (char *)scr_sa.base + ((size_t)c << scr_sa.shift); + char *top = scr_sa.bump[c]; + size_t nblocks = (size_t)(top - start) / sz; + if (!nblocks || !scr_sa.free[c]) return; + size_t words = (nblocks + 63) / 64; + uint64_t *bits = mmap(NULL, words * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (bits == MAP_FAILED) return; + for (ScrSaBlock *b = scr_sa.free[c]; b; b = b->next) { + size_t k = (size_t)((char *)b - start) / sz; + bits[k / 64] |= (uint64_t)1 << (k % 64); + } +#define SCR_SA_FREE_BIT(k) ((bits[(k) / 64] >> ((k) % 64)) & 1) + /* Mark the returned pages' blocks by clearing their bits after the + * madvise; a page is returned when every block overlapping it is free. */ + uintptr_t first = ((uintptr_t)start + page - 1) & ~(uintptr_t)(page - 1); + uintptr_t last = (uintptr_t)top & ~(uintptr_t)(page - 1); + char *run = NULL; + for (uintptr_t pg = first; pg <= last; pg += page) { + bool empty = false; + if (pg < last) { + size_t k0 = (size_t)((char *)pg - start) / sz; + size_t k1 = (size_t)((char *)pg + page - 1 - start) / sz; + if (k1 >= nblocks) k1 = nblocks - 1; + empty = true; + for (size_t k = k0; k <= k1 && empty; k++) empty = SCR_SA_FREE_BIT(k); + } + if (empty && !run) run = (char *)pg; + if (!empty && run) { + (void)madvise(run, (size_t)((char *)pg - run), MADV_DONTNEED); + /* Blocks wholly inside the returned pages are dropped. */ + size_t k0 = ((size_t)(run - start) + sz - 1) / sz; + size_t k1 = (size_t)((char *)pg - start) / sz; + for (size_t k = k0; k < k1 && (char *)start + (k + 1) * sz <= (char *)pg; k++) + bits[k / 64] &= ~((uint64_t)1 << (k % 64)); + run = NULL; + } + } + /* The kept free blocks, in address order. */ + ScrSaBlock *head = NULL, **tail = &head; + for (size_t w = 0; w < words; w++) + for (uint64_t m = bits[w]; m; m &= m - 1) { + ScrSaBlock *b = (ScrSaBlock *)(start + (w * 64 + (size_t)__builtin_ctzll(m)) * sz); + *tail = b; + tail = &b->next; + } + *tail = NULL; + scr_sa.free[c] = head; +#undef SCR_SA_FREE_BIT + (void)munmap(bits, words * 8); +} +#endif + +void scr_heap_trim(void) { +#ifdef SCR_SA_TRIM + /* A worker runtime returns the calling thread's arena pages as a parking + * thread does; the arena stays the thread's own. */ + if (scr_sa_arena >= 0 && scr_sa_trim_worth()) { + scr_sa_trim(); + return; + } +#elif SCR_SMALL_ALLOC && !defined(SCR_WORKERS) && defined(__linux__) && defined(MADV_DONTNEED) + if (scr_sa.span) { + long page = sysconf(_SC_PAGESIZE); + for (unsigned c = 0; c < SCR_SA_NCLASS; c++) + if (scr_sa.lim[c]) scr_sa_return_free_pages(c, page > 0 ? (size_t)page : 4096); + } +#endif +#if defined(__GLIBC__) && !defined(SCR_HEAP_SANITIZER) + /* Large blocks: the system allocator's free pages too. */ + (void)malloc_trim(0); +#endif +} + /* ── program-lifetime region ──────────────────────────────────────────── * Instances of compact classes (llvm/classes.ts LlClassMeta.compact) in * programs built for Linux without sanitizers (runtimes with the size-class diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index 0d9df7106f..2b942e0111 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -2350,7 +2350,9 @@ void scr_collect_cycles(void) { * finding anything, and the death rule's backoff then start over from the * heap the sweep leaves, as after a productive pass. Otherwise the next * phase's garbage would accumulate until the heap grew to that widened - * fraction. */ + * fraction. The freed memory then goes back to the system (scr_heap_trim): + * the next phase allocates other sizes than the dead data held, so it would + * reuse little of it and grow its resident memory on top. */ void scr_collect_cycles_phase(void) { scr_collect_cycles(); for (unsigned gen = SCR_CYC_MATURE; gen < SCR_CYC_NGENS; gen++) { @@ -2358,6 +2360,7 @@ void scr_collect_cycles_phase(void) { scr_cyc_growth_streak[gen] = 0; } scr_cyc_death_shift = 0; + scr_heap_trim(); } /* Worker TLS becomes unreachable when its OS thread returns. Release the diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index 34c9fdc1db..5a705054a5 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -732,6 +732,10 @@ static inline void scr_cyc_dead(void *obj) { * live heap — do not put it on a per-turn path. */ void scr_collect_cycles(void); void scr_collect_cycles_phase(void); +/* Returns the heap pages that hold only freed memory to the system: the + * calling thread's size-class pages whose blocks are all free, and the + * system allocator's free pages (scr_alloc.c). */ +void scr_heap_trim(void); /* Sets the growth cap SCR_CYCLE_GROWTH_CAP sets (scr_cycle.c), for a program * that knows its own memory profile; the environment variable takes * precedence. Call it before the program allocates much. */ diff --git a/packages/runtime/test/heap-trim.test.ts b/packages/runtime/test/heap-trim.test.ts new file mode 100644 index 0000000000..f2ec405feb --- /dev/null +++ b/packages/runtime/test/heap-trim.test.ts @@ -0,0 +1,43 @@ +import { execFile } from "node:child_process"; +import { mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const execFileAsync = promisify(execFile); +const testDir = import.meta.dirname; + +// scr_heap_trim returns the size-class pages that hold only free blocks, in +// the single-threaded allocator and in the per-thread arenas of worker +// runtimes while another thread allocates. +test.runIf(process.platform === "linux")( + "heap trim returns free pages and keeps live blocks", + async () => { + const buildDir = join(testDir, "build"); + await mkdir(buildDir, { recursive: true }); + for (const [name, flags] of [ + ["test_heap_trim", []], + ["test_heap_trim_workers", ["-DSCR_WORKERS", "-pthread"]], + ] as const) { + const bin = join(buildDir, name); + await execFileAsync("clang", [ + "-std=c11", + "-D_GNU_SOURCE", + "-O1", + "-Wall", + "-Wextra", + "-fsanitize=undefined", + ...flags, + "-o", + bin, + join(testDir, "test_heap_trim.c"), + join(testDir, "../src/scr_alloc.c"), + ]); + const run = await execFileAsync(bin, [], { + env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, + }); + expect(run.stdout).toBe("heap trim checks passed\n"); + } + }, + 120_000, +); diff --git a/packages/runtime/test/test_heap_trim.c b/packages/runtime/test/test_heap_trim.c new file mode 100644 index 0000000000..66729cd9e7 --- /dev/null +++ b/packages/runtime/test/test_heap_trim.c @@ -0,0 +1,140 @@ +/* scr_heap_trim: after most blocks of a class are freed, the pages holding + * only free blocks go back to the system, the blocks still allocated keep + * their contents, and the class serves new blocks that overlap none of + * them. Built with and without SCR_WORKERS; with it, another thread + * allocates and frees its own blocks throughout, and frees blocks of the + * trimming thread before the trim (remote frees). */ +#include "../src/scr_runtime.h" + +#include +#include +#include +#include +#include +#ifdef SCR_WORKERS +#include +#include +#endif + +#define COUNT 400000 +#define SIZE 48 +#define KEEP_EVERY 1000 + +_Noreturn void scr_trap(const char *msg) { + fputs(msg, stderr); + exit(EXIT_FAILURE); +} + +static void check(bool condition, const char *message) { + if (condition) return; + fprintf(stderr, "heap trim test failed: %s\n", message); + exit(EXIT_FAILURE); +} + +/* Resident bytes of this process. */ +static size_t resident(void) { + FILE *f = fopen("/proc/self/statm", "r"); + check(f != NULL, "open /proc/self/statm"); + unsigned long size = 0, pages = 0; + check(fscanf(f, "%lu %lu", &size, &pages) == 2, "read /proc/self/statm"); + fclose(f); + return (size_t)pages * (size_t)sysconf(_SC_PAGESIZE); +} + +#ifdef SCR_WORKERS +static atomic_bool churning = true; + +/* Allocates, fills and frees blocks of several sizes until told to stop. */ +static void *churn(void *arg) { + (void)arg; + void *held[256] = {0}; + for (size_t round = 0; atomic_load(&churning); round++) { + size_t slot = round % 256, size = 16 + (round % 7) * 24; + if (held[slot]) scr_mem_free(held[slot]); + held[slot] = scr_mem_alloc(size); + check(held[slot] != NULL, "allocate on the other thread"); + memset(held[slot], 0x33, size); + } + for (size_t i = 0; i < 256; i++) + if (held[i]) scr_mem_free(held[i]); + return NULL; +} + +/* Frees `count` blocks starting at `first` from another thread. */ +typedef struct { + unsigned char **blocks; + size_t first, count; +} RemoteFree; + +static void *free_remotely(void *arg) { + RemoteFree *r = arg; + for (size_t i = r->first; i < r->first + r->count; i++) + if (i % KEEP_EVERY != 0) { + scr_mem_free(r->blocks[i]); + r->blocks[i] = NULL; + } + return NULL; +} +#endif + +static int compare(const void *a, const void *b) { + uintptr_t x = *(const uintptr_t *)a, y = *(const uintptr_t *)b; + return x < y ? -1 : x > y; +} + +int main(void) { +#ifdef SCR_WORKERS + pthread_t churner; + check(pthread_create(&churner, NULL, churn, NULL) == 0, "start the other thread"); +#endif + unsigned char **blocks = malloc(COUNT * sizeof *blocks); + check(blocks != NULL, "block table"); + for (size_t i = 0; i < COUNT; i++) { + blocks[i] = scr_mem_alloc(SIZE); + check(blocks[i] != NULL, "allocate"); + memset(blocks[i], (int)(i & 0xff), SIZE); + } + size_t kept = 0; + uintptr_t *keep = malloc((COUNT / KEEP_EVERY + 1) * sizeof *keep); + check(keep != NULL, "keep table"); +#ifdef SCR_WORKERS + RemoteFree remote = {blocks, 0, COUNT / 4}; + pthread_t freer; + check(pthread_create(&freer, NULL, free_remotely, &remote) == 0, "start the freeing thread"); + check(pthread_join(freer, NULL) == 0, "join the freeing thread"); +#endif + for (size_t i = 0; i < COUNT; i++) { + if (i % KEEP_EVERY == 0) { + keep[kept++] = (uintptr_t)blocks[i]; + continue; + } + if (blocks[i]) scr_mem_free(blocks[i]); + } + size_t before = resident(); + scr_heap_trim(); + size_t after = resident(); + check(before > after && before - after >= (size_t)8 << 20, "the free pages go back to the system"); + for (size_t i = 0; i < COUNT; i += KEEP_EVERY) + for (size_t j = 0; j < SIZE; j++) check(blocks[i][j] == (unsigned char)(i & 0xff), "kept blocks keep their contents"); + qsort(keep, kept, sizeof *keep, compare); + for (size_t i = 0; i < COUNT; i++) { + if (i % KEEP_EVERY == 0) continue; + unsigned char *p = scr_mem_alloc(SIZE); + check(p != NULL, "allocate after the trim"); + uintptr_t key = (uintptr_t)p; + check(bsearch(&key, keep, kept, sizeof *keep, compare) == NULL, "a new block is not a kept one"); + memset(p, 0x5a, SIZE); + blocks[i] = p; + } + for (size_t i = 0; i < COUNT; i += KEEP_EVERY) + for (size_t j = 0; j < SIZE; j++) check(blocks[i][j] == (unsigned char)(i & 0xff), "kept blocks survive new allocations"); + for (size_t i = 0; i < COUNT; i++) scr_mem_free(blocks[i]); +#ifdef SCR_WORKERS + atomic_store(&churning, false); + check(pthread_join(churner, NULL) == 0, "join the other thread"); +#endif + free(keep); + free(blocks); + printf("heap trim checks passed\n"); + return 0; +} From feb9877f1ab86ef10dbeb77e4d77b6fb7f813d28 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:44:42 -0500 Subject: [PATCH 26/49] Read and write shared typed-array elements at a counted loop's counter with plain accesses In a program with workers, every element of a view that may be backed by a SharedArrayBuffer was read and written with relaxed atomics, so that a loop which reads one element again sees another thread's write. LLVM does not vectorize loops with atomic accesses: a byte loop over a shared Uint8Array ran at 8.5 ns per element in a program with workers and 0.14 ns in one without. In a counted loop that polls for cancellation between blocks of iterations, an element read or written at the loop's counter, outside any loop nested in the body, is now a plain load or store, like a thread-owned view's. The counter steps by a nonzero constant and the body does not assign it, so every iteration reaches a different element and no read is merged across iterations or moved out of the loop. Within one iteration, a racing write from another thread can be seen or missed, the documented gap of non-atomic shared accesses. Other accesses, Float16 elements and Atomics keep their atomic paths. Corpus 8332 runs these loops over shared views of each element type on a worker and the main thread, and matches Node's output. --- .../backend/llvm/bytes-loop-versions.test.ts | 9 +- .../src/backend/llvm/emit-statements.ts | 26 +++- packages/compiler/src/backend/llvm/emitter.ts | 6 + .../compiler/src/backend/llvm/expr-bytes.ts | 27 +++- .../test/counted-loop-emission.test.ts | 79 +++++++++++ .../8332-worker-shared-counted-loops.ts.json | 6 + ...orker-shared-loop-version-emission.test.ts | 8 +- .../8332-worker-shared-counted-loops.ts | 128 ++++++++++++++++++ .../8332-worker-shared-counted-loops.ts.json | 1 + 9 files changed, 275 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8332-worker-shared-counted-loops.ts.json create mode 100644 tests/corpus/8332-worker-shared-counted-loops.ts create mode 100644 tests/harness/corpus-costs/8332-worker-shared-counted-loops.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts index 0fa8fea348..742d256b79 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -320,16 +320,17 @@ test("worker programs test each receiver's shared storage once in the guard", () const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.fast\.shared\d+:$/m.exec(body)![0]; expect(fast).not.toContain("bytes.shared"); expect(fast).not.toMatch(/(load|store) atomic double/); - // The checked copy keeps the shared-memory path for each access, and so - // does the copy for shared views whose bounds hold. + // The checked copy keeps the shared-memory path for each access. The copy + // for shared views whose bounds hold is a counted loop that polls between + // blocks, so its accesses at the counter are plain. const slow = body.slice(body.search(/^bytes\.version\.slow\d+:$/m), body.indexOf(fast)); expect(slow).toContain("load atomic double"); expect(slow).toContain("store atomic double"); const shared = /^bytes\.version\.fast\.shared\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec( body, )![0]; - expect(shared).toContain("load atomic double"); - expect(shared).toContain("store atomic double"); + expect(shared).toMatch(/^loop\.poll\d+:$/m); + expect(shared).not.toMatch(/(load|store) atomic double/); }); test("index temps of a version's counter are integers in the fast copy", () => { diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index cc0af8ebb6..310c5abb4f 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1265,6 +1265,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { if (stripMined) { const counter: StripMinedCounter = countedLoop ? { + localId: countedLoop.localId, slot: integerSlot!, type: "i64", signed: true, @@ -1276,6 +1277,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { limit: () => countedLimit!, } : { + localId: integerLoop!.localId, slot: integerSlot!, type: emitter.sizeType, signed: false, @@ -2705,10 +2707,11 @@ function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { return trips * (1 + nested); } -/** The counter of a strip-mined loop: its slot, the LLVM integer type and - * signedness of its values, its step, the counters stepped beside it, and - * the limit, emitted in the outer header before each block. */ +/** The counter of a strip-mined loop: its binding, its slot, the LLVM + * integer type and signedness of its values, its step, the counters stepped + * beside it, and the limit, emitted in the outer header before each block. */ interface StripMinedCounter { + localId: string; slot: string; type: string; signed: boolean; @@ -2800,7 +2803,7 @@ function emitStripMinedCountedLoop( const after = runSlot === null ? poll : B.newLabel("loop.end"); B.condBr(inBlock, lb, after); B.startBlock(lb); - emitter.jumpTargets.push({ + const target: LlEmitter["jumpTargets"][number] = { kind: "loop", brkLabel: exit, contLabel: lu, @@ -2808,8 +2811,19 @@ function emitStripMinedCountedLoop( frameDepth: emitter.frames.length, scopeDepth: emitter.scopes.length, finallyDepth: emitter.finallyStack.length, - }); - emitter.emitBlock(s.body); + }; + emitter.jumpTargets.push(target); + // The counter steps by a nonzero constant and the body does not assign + // it, so an element at the counter is a different element on every + // iteration: its plain load or store is not merged with another + // iteration's or moved out of the loop. + const outer = emitter.plainSharedLoop; + emitter.plainSharedLoop = { counter: counter.localId, target }; + try { + emitter.emitBlock(s.body); + } finally { + emitter.plainSharedLoop = outer; + } emitter.jumpTargets.pop(); B.br(lu); B.startBlock(lu); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index b00eaa9453..095515e314 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1823,6 +1823,12 @@ export class LlEmitter { /** Worker programs: typed-array accesses whose views an enclosing version * guard proved thread-owned. */ bytesOwned: ReadonlySet = new Set(); + /** Worker programs: the strip-mined counted loop whose body is being + * emitted (emitStripMinedCountedLoop), with its counter and its jump + * target. An element access of a view that may be shared, at the counter + * and directly in that loop's body, is a plain load or store + * (plainSharedElement in expr-bytes.ts). */ + plainSharedLoop: { counter: string; target: LlEmitter["jumpTargets"][number] } | null = null; /** The alias-scope attachment of each typed-array element read and write * in the current loop version's fast copy (emitVersionedLoop), and the * one the element access being emitted carries (bytesElementAlias). */ diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index b380767d4f..10363c19c5 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -317,7 +317,7 @@ function emitBytesLoad( owned: boolean, ): LlValue { // `owned`: an enclosing version guard proved the view thread-owned. - if (host.mod.workers && !owned) { + if (host.mod.workers && !owned && !plainSharedElement(host, elem, expr)) { const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds); return emitSharedOrLocalLoad(host, elem, receiver, idx); } @@ -328,6 +328,29 @@ function emitBytesLoad( return emitBytesLoadAt(host, elem, data, idx); } +/** Worker programs: whether an element access at `index` of a view that + * may be shared is a plain load or store, like a thread-owned view's. It is + * when the index is the counter of the strip-mined counted loop whose body + * holds the access, outside any loop nested in that body: every iteration + * then reaches a different element, so LLVM cannot merge an element's reads + * across iterations or move them out of the loop, and the loop can be + * vectorized. A racing write from another thread can still be seen or + * missed within one iteration, the documented gap of non-atomic shared + * accesses. Float16 elements keep their runtime path. */ +function plainSharedElement( + host: LlvmEmitterContext, + elem: IrBytesElem, + index: IrExpr | undefined, +): boolean { + const loop = host.plainSharedLoop; + if (loop === null || elem === "f16" || index?.kind !== "varRef") return false; + if (index.localId !== loop.counter) return false; + let innermost: (typeof host.jumpTargets)[number] | undefined; + for (let i = host.jumpTargets.length - 1; i >= 0 && innermost === undefined; i--) + if (host.jumpTargets[i]!.kind === "loop") innermost = host.jumpTargets[i]; + return innermost === loop.target; +} + /** Worker programs: the element at a checked index of a view that may be * shared. An integer element joins as its 32-bit value and converts to a * number after the join, as the local load does, so compares and @@ -666,7 +689,7 @@ function emitScopedBytesSet( ty = "i32"; operand = stored!; } - if (host.mod.workers && !owned) { + if (host.mod.workers && !owned && !plainSharedElement(host, elem, indexExpr)) { // A shared view's element takes a relaxed atomic store of its width. const local = emitSharedBytesSplit(host, receiver); const p = B.tmp(); diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index d7ebc97fad..fe48deeeb5 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -244,3 +244,82 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano await rm(dir, { recursive: true, force: true }); } }); + +test("worker programs read and write shared elements at a strip-mined loop's counter with plain accesses", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-counted-shared-")); + try { + const entry = join(dir, "shared.ts"); + await writeFile( + entry, + [ + 'import { isMainThread, Worker } from "node:worker_threads";', + "export function scale(a: Float64Array, b: Float64Array, n: number): void {", + " for (let i = 0; i < n; i++) b[i] = a[i]! * 2;", + "}", + "export function codes(a: Uint8Array): void {", + " for (let i = 0; i < a.length; ++i) a[i] = a[i] === 97 ? 0 : 3;", + "}", + "export function shift(a: Uint8Array): void {", + " for (let i = 0; i < a.length - 1; i++) a[i] = a[i + 1]!;", + "}", + "export function rows(a: Int32Array, n: number): number {", + " let t = 0;", + " for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) t += a[i]! + a[j]!;", + " return t;", + "}", + "export function spin(a: Int32Array): number {", + " let n = 0;", + " while (a[0] === 0 && n < 10) n++;", + " return n;", + "}", + "const a = new Float64Array(new SharedArrayBuffer(64)).fill(1);", + "const b = new Float64Array(8);", + "scale(a, b, 8);", + "const c = new Uint8Array(new SharedArrayBuffer(8)).fill(97);", + "codes(c);", + "shift(c);", + "const d = new Int32Array(new SharedArrayBuffer(16)).fill(1);", + "console.log(b[7], c[0], rows(d, 4), spin(d));", + "if (!isMainThread) new Worker(new URL(import.meta.url));", + ].join("\n"), + ); + const outPath = join(dir, "shared.ir.json"); + const result = await compile(entry, { outPath, outDir: dir, outputKind: "ir" }); + if (!result.ok) throw new Error(result.diagnostics.map((d) => d.message).join("\n")); + const ll = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + const bodies = (name: string): string => + [ + ...ll.matchAll( + new RegExp( + `define internal [^\\n]+ @sc_b?f_${name}(?:\\.i32)?\\([^\\n]*\\n([\\s\\S]*?)\\n\\}`, + "g", + ), + ), + ] + .map((match) => match[1]!) + .join("\n"); + // Element loads (the poll's flag read is atomic too, from a global). + const elementLoads = (body: string): number => + body.match(/load atomic \w+, ptr %/g)?.length ?? 0; + // Elements at the counter of a strip-mined loop: plain loads and stores. + for (const name of ["scale", "codes"]) { + const body = bodies(name); + expect(body).toMatch(/^loop\.poll\d+:$/m); + expect(elementLoads(body)).toBe(0); + expect(body).not.toContain("store atomic"); + } + // `a[i + 1]` is not at the counter: its read stays atomic, the store + // at the counter is plain. + const shift = bodies("shift"); + expect(shift).toMatch(/load atomic i8, ptr %/); + expect(shift).not.toContain("store atomic"); + // The outer counter read inside the inner loop reads one element on every + // inner iteration: it stays atomic; the inner counter's read is plain. + const rows = bodies("rows"); + expect(rows).toMatch(/load atomic i32, ptr %/); + // A loop that is not counted keeps atomic reads. + expect(bodies("spin")).toMatch(/load atomic i32, ptr %/); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8332-worker-shared-counted-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8332-worker-shared-counted-loops.ts.json new file mode 100644 index 0000000000..57308033d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8332-worker-shared-counted-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8332-worker-shared-counted-loops.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/worker-shared-loop-version-emission.test.ts b/packages/compiler/test/worker-shared-loop-version-emission.test.ts index 7eebcc2e4f..ad7ec1ae97 100644 --- a/packages/compiler/test/worker-shared-loop-version-emission.test.ts +++ b/packages/compiler/test/worker-shared-loop-version-emission.test.ts @@ -56,11 +56,13 @@ if (isMainThread) { // The guard tests the bounds first, then whether the view is shared. expect(fn).toMatch(/^bytes\.version\.owned\d*:/m); expect(fn).toMatch(/^bytes\.version\.fast\.shared\d*:/m); - // The shared copy reads with no bounds test, and shared-safe: a view - // with shared storage loads atomically. + // The shared copy reads with no bounds test. Each copy is a counted loop + // that polls between blocks, so its read at the counter is a plain load + // (plainSharedElement in expr-bytes.ts). const shared = blocks(fn, "bytes.version.fast.shared"); expect(shared).not.toMatch(/icmp (ult|uge|ugt) i64 %\w+, %\w+/); - expect(fn).toMatch(/load atomic double, ptr %\w+ monotonic/); + expect(fn).toMatch(/load double, ptr %\w+/); + expect(fn).not.toMatch(/load atomic double/); }); test("a program without workers keeps two copies of the loop", async () => { diff --git a/tests/corpus/8332-worker-shared-counted-loops.ts b/tests/corpus/8332-worker-shared-counted-loops.ts new file mode 100644 index 0000000000..010531c139 --- /dev/null +++ b/tests/corpus/8332-worker-shared-counted-loops.ts @@ -0,0 +1,128 @@ +// Counted loops over typed arrays backed by a SharedArrayBuffer in a program +// with workers: loops to a length and to a bound, ascending, descending and +// by two, reads and writes at the counter and next to it, nested loops, and +// each element type. The worker and the main thread run the same loops over +// the same shared memory, one after the other. +import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads"; + +interface Shared { + bytes: Uint8Array; + shorts: Int16Array; + words: Uint32Array; + ints: Int32Array; + floats: Float32Array; + doubles: Float64Array; +} + +function codes(a: Uint8Array): void { + for (let i = 0; i < a.length; ++i) { + const x = a[i]; + if (x === 97) a[i] = 0; + else if (x === 116) a[i] = 1; + else if (x === 99) a[i] = 2; + else a[i] = 3; + } +} + +function checksum(a: Uint8Array): number { + let t = 0; + for (let i = 0; i < a.length; i++) t = (t * 31 + a[i]!) % 1000003; + return t; +} + +function shiftLeft(a: Uint8Array): void { + for (let i = 0; i < a.length - 1; i++) a[i] = a[i + 1]!; +} + +function descend(a: Int16Array, n: number): number { + let t = 0; + for (let i = n - 1; i >= 0; i--) { + a[i] = a[i]! - i * 3; + t += a[i]!; + } + return t; +} + +function everyOther(a: Uint32Array): number { + let t = 0; + for (let i = 0; i < a.length; i += 2) { + a[i] = a[i]! * 2 + 4000000000; + t += a[i]! % 1000; + } + return t; +} + +function grid(a: Int32Array, n: number): number { + let t = 0; + for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) t += a[i]! * a[j]! - j; + return t; +} + +function mix(f: Float32Array, d: Float64Array, n: number): number { + for (let i = 0; i < n; i++) { + f[i] = f[i]! * 0.5 + i; + d[i] = d[i]! + f[i]! / 3; + } + let t = 0; + for (let i = 0; i < n; i++) t += d[i]!; + return Math.round(t * 1000) / 1000; +} + +function fill(s: Shared): void { + const letters = [97, 116, 99, 103]; + let seed = 7; + for (let i = 0; i < s.bytes.length; i++) { + seed = (seed * 1103515245 + 12345) % 2147483648; + s.bytes[i] = letters[(seed >> 16) % 4]!; + } + for (let i = 0; i < s.shorts.length; i++) s.shorts[i] = (i * 37) % 2000 - 1000; + for (let i = 0; i < s.words.length; i++) s.words[i] = i * 2654435761; + for (let i = 0; i < s.ints.length; i++) s.ints[i] = (i % 7) - 3; + for (let i = 0; i < s.floats.length; i++) s.floats[i] = i / 4; + for (let i = 0; i < s.doubles.length; i++) s.doubles[i] = i * 1.25; +} + +function run(where: string, s: Shared): string { + codes(s.bytes); + const coded = checksum(s.bytes); + shiftLeft(s.bytes); + const shifted = checksum(s.bytes); + const down = descend(s.shorts, s.shorts.length); + const other = everyOther(s.words); + const cells = grid(s.ints, 40); + const mixed = mix(s.floats, s.doubles, s.floats.length); + return `${where}: ${coded} ${shifted} ${down} ${other} ${cells} ${mixed} ${s.bytes[0]} ${s.words[2]}`; +} + +function views(buffers: SharedArrayBuffer[]): Shared { + return { + bytes: new Uint8Array(buffers[0]!), + shorts: new Int16Array(buffers[1]!), + words: new Uint32Array(buffers[2]!), + ints: new Int32Array(buffers[3]!), + floats: new Float32Array(buffers[4]!), + doubles: new Float64Array(buffers[5]!), + }; +} + +if (isMainThread) { + const buffers = [ + new SharedArrayBuffer(10007), + new SharedArrayBuffer(2 * 515), + new SharedArrayBuffer(4 * 333), + new SharedArrayBuffer(4 * 40), + new SharedArrayBuffer(4 * 129), + new SharedArrayBuffer(8 * 129), + ]; + const shared = views(buffers); + fill(shared); + const worker = new Worker(new URL(import.meta.url), { workerData: buffers }); + worker.on("message", (line: string) => { + console.log(line); + console.log(run("main", shared)); + fill(shared); + console.log(run("main again", shared)); + }); +} else { + parentPort!.postMessage(run("worker", views(workerData as SharedArrayBuffer[]))); +} diff --git a/tests/harness/corpus-costs/8332-worker-shared-counted-loops.ts.json b/tests/harness/corpus-costs/8332-worker-shared-counted-loops.ts.json new file mode 100644 index 0000000000..6b0f0395b6 --- /dev/null +++ b/tests/harness/corpus-costs/8332-worker-shared-counted-loops.ts.json @@ -0,0 +1 @@ +{ "plain": 4011, "san": 2208 } From 307f9e00093a123c51b75ea7ffa64ac8b6c92c9d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:47:07 -0500 Subject: [PATCH 27/49] Keep one heap-trim test block per four pages of any size The test kept one live 48-byte block every 1,000 allocations, which on a system with 64 KiB pages leaves a live block on every page, so no page held only free blocks and the resident-memory check failed. It now keeps one block per four pages of blocks, from the system's page size. --- packages/runtime/test/test_heap_trim.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/runtime/test/test_heap_trim.c b/packages/runtime/test/test_heap_trim.c index 66729cd9e7..afd1404301 100644 --- a/packages/runtime/test/test_heap_trim.c +++ b/packages/runtime/test/test_heap_trim.c @@ -18,13 +18,17 @@ #define COUNT 400000 #define SIZE 48 -#define KEEP_EVERY 1000 _Noreturn void scr_trap(const char *msg) { fputs(msg, stderr); exit(EXIT_FAILURE); } +/* One block kept per KEEP_PAGES pages of blocks, whatever the page size: a + * kept block pins its page, so the others hold only free blocks. */ +#define KEEP_PAGES 4 +static size_t keep_every; + static void check(bool condition, const char *message) { if (condition) return; fprintf(stderr, "heap trim test failed: %s\n", message); @@ -69,7 +73,7 @@ typedef struct { static void *free_remotely(void *arg) { RemoteFree *r = arg; for (size_t i = r->first; i < r->first + r->count; i++) - if (i % KEEP_EVERY != 0) { + if (i % keep_every != 0) { scr_mem_free(r->blocks[i]); r->blocks[i] = NULL; } @@ -83,6 +87,7 @@ static int compare(const void *a, const void *b) { } int main(void) { + keep_every = KEEP_PAGES * ((size_t)sysconf(_SC_PAGESIZE) / SIZE + 1); #ifdef SCR_WORKERS pthread_t churner; check(pthread_create(&churner, NULL, churn, NULL) == 0, "start the other thread"); @@ -95,7 +100,7 @@ int main(void) { memset(blocks[i], (int)(i & 0xff), SIZE); } size_t kept = 0; - uintptr_t *keep = malloc((COUNT / KEEP_EVERY + 1) * sizeof *keep); + uintptr_t *keep = malloc((COUNT / keep_every + 1) * sizeof *keep); check(keep != NULL, "keep table"); #ifdef SCR_WORKERS RemoteFree remote = {blocks, 0, COUNT / 4}; @@ -104,7 +109,7 @@ int main(void) { check(pthread_join(freer, NULL) == 0, "join the freeing thread"); #endif for (size_t i = 0; i < COUNT; i++) { - if (i % KEEP_EVERY == 0) { + if (i % keep_every == 0) { keep[kept++] = (uintptr_t)blocks[i]; continue; } @@ -114,11 +119,11 @@ int main(void) { scr_heap_trim(); size_t after = resident(); check(before > after && before - after >= (size_t)8 << 20, "the free pages go back to the system"); - for (size_t i = 0; i < COUNT; i += KEEP_EVERY) + for (size_t i = 0; i < COUNT; i += keep_every) for (size_t j = 0; j < SIZE; j++) check(blocks[i][j] == (unsigned char)(i & 0xff), "kept blocks keep their contents"); qsort(keep, kept, sizeof *keep, compare); for (size_t i = 0; i < COUNT; i++) { - if (i % KEEP_EVERY == 0) continue; + if (i % keep_every == 0) continue; unsigned char *p = scr_mem_alloc(SIZE); check(p != NULL, "allocate after the trim"); uintptr_t key = (uintptr_t)p; @@ -126,7 +131,7 @@ int main(void) { memset(p, 0x5a, SIZE); blocks[i] = p; } - for (size_t i = 0; i < COUNT; i += KEEP_EVERY) + for (size_t i = 0; i < COUNT; i += keep_every) for (size_t j = 0; j < SIZE; j++) check(blocks[i][j] == (unsigned char)(i & 0xff), "kept blocks survive new allocations"); for (size_t i = 0; i < COUNT; i++) scr_mem_free(blocks[i]); #ifdef SCR_WORKERS From 43d207ce38529112482c880a2c0b57f6da7e900e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:52:33 -0500 Subject: [PATCH 28/49] Compare canonical types structurally and keep tuple flags in the normalizer - The intern key names bound type parameters by identity, not by name, so environments that bind two parameters called T to swapped types no longer share a node. - Type equality (intersection constituents, union arms) is a structural comparison, canonEqual, that compares signature parameters, index and construct signatures at any depth and closes on recursive types. canonKey no longer serves as equality, and now prints parameter types, construct and index signatures. - An object with index or construct signatures is not the empty object of a narrowed parameter, so a dictionary intersected with named properties keeps its index signature. - Arrays and tuples enter the intern table before their elements are normalized, so recursive array and tuple aliases close on themselves. - Tuple elements read their optional and rest flags from the tuple's element flags. --- packages/compiler/src/frontend/canon.ts | 170 +++++++++++++++--- .../compiler/test/canon-normalize.test.ts | 91 ++++++++++ 2 files changed, 241 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/frontend/canon.ts b/packages/compiler/src/frontend/canon.ts index 833d130813..7c1ac30999 100644 --- a/packages/compiler/src/frontend/canon.ts +++ b/packages/compiler/src/frontend/canon.ts @@ -103,14 +103,119 @@ export function canonKey(c: Canon, depth = 0): string { const props = c .props() .map((p) => `${p.name}${p.optional ? "?" : ""}: ${canonKey(p.type(), depth + 1)}`); - const calls = c.call().map((s) => `(${s.params.length}) => ${canonKey(s.ret(), depth + 1)}`); - return `{ ${[...calls, ...props].join("; ")} }`; + const sig = (s: CanonSig): string => + `(${s.params + .map( + (p) => `${p.rest ? "..." : ""}${p.optional ? "?" : ""}${canonKey(p.type(), depth + 1)}`, + ) + .join(", ")}) => ${canonKey(s.ret(), depth + 1)}`; + const calls = c.call().map(sig); + const constructs = c.construct().map((s) => `new ${sig(s)}`); + const index = c + .index() + .map((i) => `[${canonKey(i.key, depth + 1)}]: ${canonKey(i.value(), depth + 1)}`); + return `{ ${[...calls, ...constructs, ...index, ...props].join("; ")} }`; + } + } +} + +/** Whether two canonical nodes are the same type, structurally: unlike + * canonKey, every part is compared (signature parameters, index + * signatures, any depth). A pair met again while it is being compared is + * taken as equal, which closes the comparison of recursive types. */ +export function canonEqual( + a: Canon, + b: Canon, + assumed: Map> = new Map(), +): boolean { + if (a === b) return true; + if (a.k !== b.k) return false; + let pairs = assumed.get(a); + if (pairs?.has(b)) return true; + if (pairs === undefined) assumed.set(a, (pairs = new Set())); + pairs.add(b); + const eq = (x: Canon, y: Canon): boolean => canonEqual(x, y, assumed); + const sigsEqual = (xs: readonly CanonSig[], ys: readonly CanonSig[]): boolean => + xs.length === ys.length && + xs.every((x, i) => { + const y = ys[i]!; + return ( + x.generic === y.generic && + x.params.length === y.params.length && + x.params.every( + (p, j) => + p.optional === y.params[j]!.optional && + p.rest === y.params[j]!.rest && + eq(p.type(), y.params[j]!.type()), + ) && + eq(x.ret(), y.ret()) + ); + }); + switch (a.k) { + case "prim": + return a.p === (b as typeof a).p; + case "lit": + return a.value === (b as typeof a).value; + case "unit": + return a.u === (b as typeof a).u; + case "top": + return a.t === (b as typeof a).t; + case "param": + return a.name === (b as typeof a).name; + case "opaque": + return a.ts === (b as typeof a).ts; + case "array": { + const o = b as typeof a; + return a.readonly === o.readonly && eq(a.elem, o.elem); + } + case "tuple": { + const o = b as typeof a; + return ( + a.elems.length === o.elems.length && + a.elems.every( + (e, i) => + e.optional === o.elems[i]!.optional && + e.rest === o.elems[i]!.rest && + eq(e.type, o.elems[i]!.type), + ) + ); + } + case "union": { + const o = b as typeof a; + return ( + a.arms.length === o.arms.length && + a.arms.every((x) => o.arms.some((y) => eq(x, y))) && + o.arms.every((y) => a.arms.some((x) => eq(x, y))) + ); + } + case "obj": { + const o = b as typeof a; + if (a.nominal !== o.nominal) return false; + if (a.nominal !== undefined) return true; + const ap = a.props(); + const op = o.props(); + if (ap.length !== op.length) return false; + for (const p of ap) { + const q = op.find((x) => x.name === p.name); + if (q === undefined || q.optional !== p.optional || !eq(p.type(), q.type())) return false; + } + const ai = a.index(); + const oi = o.index(); + return ( + ai.length === oi.length && + ai.every((x, i) => eq(x.key, oi[i]!.key) && eq(x.value(), oi[i]!.value())) && + sigsEqual(a.call(), o.call()) && + sigsEqual(a.construct(), o.construct()) + ); } } } export class Normalizer { private readonly interned = new Map(); + /** A number for each type parameter an environment binds: parameters + * with the same name are distinct keys. */ + private readonly paramIds = new Map(); constructor(private readonly checker: ts.TypeChecker) {} @@ -129,8 +234,11 @@ export class Normalizer { private envKey(env: CanonEnv): string { if (env.size === 0) return ""; const parts: string[] = []; - for (const [param, bound] of env) - parts.push(`${ts.symbolName(param)}=${(bound as { id?: number }).id ?? "?"}`); + for (const [param, bound] of env) { + let id = this.paramIds.get(param); + if (id === undefined) this.paramIds.set(param, (id = this.paramIds.size)); + parts.push(`${id}=${(bound as { id?: number }).id ?? "?"}`); + } return parts.sort().join(","); } @@ -169,7 +277,6 @@ export class Normalizer { /** Flat, deduplicated, never-free; `void` reads as `undefined`. */ private union(parts: readonly ts.Type[], env: CanonEnv): Canon { const arms: Canon[] = []; - const seen = new Set(); const add = (c: Canon): void => { if (c.k === "union") { for (const a of c.arms) add(a); @@ -177,11 +284,9 @@ export class Normalizer { } if (c.k === "unit" && c.u === "never") return; const arm: Canon = c.k === "unit" && c.u === "void" ? { k: "unit", u: "undefined" } : c; - // Objects dedupe by identity (they intern by checker type): their - // structural key would walk into a recursive type still being built. - const k = arm.k === "obj" ? arm : canonKey(arm); - if (seen.has(k)) return; - seen.add(k); + // Objects dedupe by identity (they intern by checker type): comparing + // their members would walk into a recursive type still being built. + if (arms.some((a) => (arm.k === "obj" ? a === arm : canonEqual(a, arm)))) return; arms.push(arm); }; for (const part of parts) add(this.canon(part, env)); @@ -206,12 +311,16 @@ export class Normalizer { * primitive branded with an object is the primitive. */ private intersection(type: ts.Type, env: CanonEnv, key: string | null): Canon { const parts = ts.constituentTypes(type).map((t) => this.canon(t, env)); - const keys = new Set(parts.map((p) => canonKey(p))); - if (keys.size === 1) return parts[0]!; + if (parts.every((p) => canonEqual(p, parts[0]!))) return parts[0]!; // Narrowed parameters: `T & {}` (and `T & ({} | null)`'s object part) // keep T without its undefined and null arms. const empty = (p: Canon): boolean => - p.k === "obj" && p.nominal === undefined && p.props().length === 0 && p.call().length === 0; + p.k === "obj" && + p.nominal === undefined && + p.props().length === 0 && + p.index().length === 0 && + p.call().length === 0 && + p.construct().length === 0; const rest = parts.filter((p) => !empty(p)); if (rest.length === 1 && rest.length < parts.length) { const only = rest[0]!; @@ -228,21 +337,42 @@ export class Normalizer { private object(type: ts.Type, env: CanonEnv, key: string | null): Canon { const checker = this.checker; + // Arrays and tuples enter the intern table before their elements are + // normalized, so a recursive alias (`type Tree = Tree[] | number`) + // closes on the node under construction. if (checker.isArrayType(type)) { const [elem] = checker.getTypeArguments(type as ts.TypeReference); - return { + const node: Canon & { k: "array" } = { k: "array", - elem: elem === undefined ? { k: "top", t: "unknown" } : this.canon(elem, env), + elem: { k: "top", t: "unknown" }, readonly: type.getSymbol() !== undefined && ts.symbolName(type.getSymbol()!) === "ReadonlyArray", }; + if (key !== null) this.interned.set(key, node); + if (elem !== undefined) node.elem = this.canon(elem, env); + return node; } if (checker.isTupleType(type)) { - const args = checker.getTypeArguments(type as ts.TypeReference); - return { - k: "tuple", - elems: args.map((a) => ({ type: this.canon(a, env), optional: false, rest: false })), - }; + const ref = type as ts.TupleTypeReference; + // The element flags live on the tuple shape: the type itself, or the + // target of a reference to it. + const shape = + (ref.elementFlags as ts.ElementFlags[] | undefined) !== undefined + ? ref + : (ref.getTarget() as ts.TupleType | undefined); + const flags = shape?.elementFlags ?? []; + const elems: { type: Canon; optional: boolean; rest: boolean }[] = []; + const node: Canon = { k: "tuple", elems }; + if (key !== null) this.interned.set(key, node); + checker.getTypeArguments(ref).forEach((a, i) => { + const f = flags[i] ?? ts.ElementFlags.Required; + elems.push({ + type: this.canon(a, env), + optional: (f & ts.ElementFlags.Optional) !== 0, + rest: (f & (ts.ElementFlags.Rest | ts.ElementFlags.Variadic)) !== 0, + }); + }); + return node; } const symbol = type.getSymbol(); const decl = symbol?.declarations[0]; diff --git a/packages/compiler/test/canon-normalize.test.ts b/packages/compiler/test/canon-normalize.test.ts index c432419b78..2ec669c397 100644 --- a/packages/compiler/test/canon-normalize.test.ts +++ b/packages/compiler/test/canon-normalize.test.ts @@ -12,6 +12,7 @@ let checker: ts.TypeChecker; const aliases = new Map(); const asTypes = new Map(); const valueTypes = new Map(); +const typeParams = new Map(); beforeAll(() => { directory = mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-canon-")); @@ -45,6 +46,23 @@ beforeAll(() => { export class Point { x = 0 } export type Points = readonly Point[]; export type Pair = [string, number]; + // Two type parameters named T, in different functions. + export function first(x: unknown) { + return x as T[]; + } + export function second(x: unknown) { + return x as readonly T[]; + } + // Distinct callables, and arrays of them. + export type Calls = ((a: string) => void) & ((a: number) => void); + export type CallArrays = ((a: string) => void)[] | ((a: number) => void)[]; + // A dictionary with a named property. + export type Dict = { [key: string]: number } & { size: number }; + // Recursive array and tuple aliases. + export type Nested = number | Nested[]; + export type Chain = [number, Chain] | null; + // Optional and rest tuple elements. + export type Loose = [string, number?, ...boolean[]]; `, ); load = loadProgram(entry); @@ -67,6 +85,11 @@ beforeAll(() => { params, }); } + if (ts.isTypeParameterDeclaration(node) && ts.isFunctionDeclaration(node.parent)) + typeParams.set( + `${node.parent.name?.text}.${node.name.text}`, + checker.getSymbolAtLocation(node.name)!, + ); if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) valueTypes.set(node.name.text, checker.getTypeAtLocation(node.name)); if ( @@ -152,6 +175,74 @@ describe("canon", () => { ); }); + test("type parameters with the same name are distinct in the intern key", () => { + const n = new Normalizer(checker); + const { type } = asTypes.get("T[]")!; + const firstT = typeParams.get("first.T")!; + const secondT = typeParams.get("second.T")!; + const num = valueTypes.get("sampleNumber")!; + const text = valueTypes.get("sampleText")!; + expect( + key( + n.canon( + type, + new Map([ + [firstT, num], + [secondT, text], + ]), + ), + ), + ).toBe("number[]"); + expect( + key( + n.canon( + type, + new Map([ + [firstT, text], + [secondT, num], + ]), + ), + ), + ).toBe("string[]"); + }); + + test("callables and arrays of callables with different parameters stay distinct", () => { + const n = new Normalizer(checker); + const calls = n.canon(aliases.get("Calls")!); + expect(calls.k === "obj" && calls.call().length).toBe(2); + const arms = n.canon(aliases.get("CallArrays")!); + expect(arms.k === "union" && arms.arms.length).toBe(2); + expect(key(arms)).toBe("{ (string) => void }[] | { (number) => void }[]"); + }); + + test("intersecting a dictionary with a named property keeps the index signature", () => { + const dict = new Normalizer(checker).canon(aliases.get("Dict")!); + expect(key(dict)).toBe("{ [string]: number; size: number }"); + }); + + test("recursive array and tuple aliases close on themselves", () => { + const n = new Normalizer(checker); + const nested = n.canon(aliases.get("Nested")!); + expect(nested.k).toBe("union"); + if (nested.k !== "union") return; + // The array closes on itself through the union of its element. + const array = nested.arms.find((a) => a.k === "array"); + const elem = array?.k === "array" ? array.elem : undefined; + expect(elem?.k === "union" && elem.arms.includes(array!)).toBe(true); + const chain = n.canon(aliases.get("Chain")!); + expect(chain.k).toBe("union"); + if (chain.k !== "union") return; + const tuple = chain.arms.find((a) => a.k === "tuple"); + const rest = tuple?.k === "tuple" ? tuple.elems[1]!.type : undefined; + expect(rest?.k === "union" && rest.arms.includes(tuple!)).toBe(true); + }); + + test("optional and rest tuple elements keep their flags", () => { + expect(key(new Normalizer(checker).canon(aliases.get("Loose")!))).toBe( + "[string, number | undefined?, ...boolean]", + ); + }); + test("classes are nominal; arrays and tuples keep their elements", () => { const n = new Normalizer(checker); expect(key(n.canon(aliases.get("Points")!))).toBe("readonly Point[]"); From a21f4f6823e45138ef8eb776c60f9a85d7994a80 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 22:55:22 -0500 Subject: [PATCH 29/49] Localize merged program shards in process on Linux A dev build splits a large program into LLVM shards, compiles them in parallel and merges the objects with ld -r. On a native Linux host the merge then ran objcopy --keep-global-symbols to turn the shards' promoted symbols back into locals. A sanitized build of three.js's node materials merges into a 660 MB object with 278k sections, and objcopy spent 425 s of a 616 s build on it. localizeElfObject, which cross-ELF builds already use, does the same demotion in about 2 s. - Program-shard merges for executables, library archives and packed libraries use localizeElfObject after ld -r on native Linux. Library runtime localization keeps objcopy. - localizeElfObject reads and writes extended section numbering: with SHN_LORESERVE or more sections the counts go in section 0, and symbol section indices from SHN_LORESERVE up go in .symtab_shndx. Before, it truncated e_shnum and read such indices as reserved values. - The Linux shard-merge cache identity drops objcopy. --- packages/compiler/src/backend/library-pack.ts | 1 + .../compiler/src/backend/native/executable.ts | 2 + .../compiler/src/backend/native/library.ts | 2 + .../src/backend/native/object-merge.ts | 36 ++++-- .../src/backend/native/program-shards.ts | 12 +- .../src/backend/object-localize.test.ts | 113 ++++++++++++++++-- .../compiler/src/backend/object-localize.ts | 88 ++++++++++++-- 7 files changed, 215 insertions(+), 39 deletions(-) diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 521f1d53ec..14ffdff28b 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -166,6 +166,7 @@ export async function compilePackedLibrary( options.programPublicSymbols, stem, target.platform, + { inProcessElf: true }, ); } else { program = await emit( diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 6444b4c3b3..abacd5c8c1 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -1519,6 +1519,8 @@ export async function compileCInternal( [], programPublicSymbols, `${stem}.program`, + undefined, + { inProcessElf: true }, ); const publishable = entries.filter((entry) => entry.missed); if (publishable.length > 0) { diff --git a/packages/compiler/src/backend/native/library.ts b/packages/compiler/src/backend/native/library.ts index f0577f3ca9..06b4ce1df9 100644 --- a/packages/compiler/src/backend/native/library.ts +++ b/packages/compiler/src/backend/native/library.ts @@ -517,6 +517,8 @@ export async function compileLibArchive(opts: LibArchiveOptions): Promise [], programPublicSymbols!, `${stem}.program`, + undefined, + { inProcessElf: true }, ); // Keep the canonical archive member spelling. The fact that native // compilation used shards is an implementation detail; consumers and diff --git a/packages/compiler/src/backend/native/object-merge.ts b/packages/compiler/src/backend/native/object-merge.ts index 39a14785ec..43f15fef8f 100644 --- a/packages/compiler/src/backend/native/object-merge.ts +++ b/packages/compiler/src/backend/native/object-merge.ts @@ -5,6 +5,17 @@ import { localizeElfObject, mergeAndLocalizeCoffObjects } from "../object-locali import { type CcDriver, targetPlatform } from "./driver.js"; import { execFileAsync, subprocessFailureDetail } from "./process.js"; +/* Demotes in process (localizeElfObject), rewriting the object in place. */ +async function localizeInProcess(object: string, keepSymbols: readonly string[]): Promise { + try { + await writeFile(object, localizeElfObject(await readFile(object), new Set(keepSymbols))); + } catch (err) { + throw new Error( + `ELF symbol localization failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n\n${(err as Error).message}`, + ); + } +} + /* Multi-instance library mode's localization step: combine the program * object with exactly the runtime/vendor members it reaches into ONE * relocatable object, then demote every scriptc external definition except @@ -46,7 +57,11 @@ import { execFileAsync, subprocessFailureDetail } from "./process.js"; * multi-archive link from discarding one archive's copies), * then binutils objcopy --keep-global-symbols localizes every * other DEFINED global (objcopy leaves undefined symbols - * global by its own rule). The exact historical recipe. + * global by its own rule). The exact historical recipe. With + * `inProcessElf` (program-shard merges), localizeElfObject + * demotes instead of objcopy: a sanitized dev program merges + * into an object with hundreds of thousands of sections, which + * objcopy takes minutes over and localizeElfObject seconds. * ELF, cross triples from any host — `zig cc -target -r` * merges (zig is already the cross driver's hard requirement), * then localizeElfObject demotes in process and resolves @@ -68,6 +83,7 @@ export async function localizeLibraryObjects( keepSymbols: readonly string[], stem: string, platform = targetPlatform(driver), + options: { inProcessElf?: boolean } = {}, ): Promise { const combined = join(buildDir, `${stem}.localized.o`); const staging = join(buildDir, `${stem}.localize-staging.a`); @@ -79,7 +95,7 @@ export async function localizeLibraryObjects( const stderr = subprocessFailureDetail(err); throw new Error( `${argv[0]} failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n` + - `Runtime symbol localization needs the ${platform === "darwin" ? "host toolchain's ld" : driver.target === null ? "host toolchain's ld and objcopy" : "cross driver's relocatable link"} beside the C compiler.\n\n${stderr}`, + `Runtime symbol localization needs the ${platform === "darwin" || (driver.target === null && options.inProcessElf === true) ? "host toolchain's ld" : driver.target === null ? "host toolchain's ld and objcopy" : "cross driver's relocatable link"} beside the C compiler.\n\n${stderr}`, ); } }; @@ -122,7 +138,6 @@ export async function localizeLibraryObjects( keepFile, ]); } else if (platform === "linux" && driver.target === null) { - await writeFile(keepFile, keepSymbols.map((s) => `${s}\n`).join("")); await run([ "ld", "-r", @@ -132,7 +147,12 @@ export async function localizeLibraryObjects( "-o", combined, ]); - await run(["objcopy", `--keep-global-symbols=${keepFile}`, combined]); + if (options.inProcessElf === true) { + await localizeInProcess(combined, keepSymbols); + } else { + await writeFile(keepFile, keepSymbols.map((s) => `${s}\n`).join("")); + await run(["objcopy", `--keep-global-symbols=${keepFile}`, combined]); + } } else if (platform === "linux") { // Cross ELF: the cross driver's own lld performs the relocatable merge // (with the staging archive's member semantics); demotion and section- @@ -152,13 +172,7 @@ export async function localizeLibraryObjects( "-o", combined, ]); - try { - await writeFile(combined, localizeElfObject(await readFile(combined), new Set(keepSymbols))); - } catch (err) { - throw new Error( - `ELF symbol localization failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n\n${(err as Error).message}`, - ); - } + await localizeInProcess(combined, keepSymbols); } else { throw new InternalCompilerError( `runtime symbol localization (abi.localize_runtime) has no ${platform} arm; compileLibrary admits darwin, linux, and win32 builds only`, diff --git a/packages/compiler/src/backend/native/program-shards.ts b/packages/compiler/src/backend/native/program-shards.ts index 1afd87f7bc..2948299b22 100644 --- a/packages/compiler/src/backend/native/program-shards.ts +++ b/packages/compiler/src/backend/native/program-shards.ts @@ -52,13 +52,13 @@ export async function resolveProgramShardMergeIdentity(driver: CcDriver): Promis } if (platform === "linux") { if (driver.target === null) { - const [ld, objcopy] = await Promise.all([resolvedTool("ld"), resolvedTool("objcopy")]); - return ld === null || objcopy === null + // ld -r merges; localizeElfObject demotes in process. + const ld = await resolvedTool("ld"); + return ld === null ? null - : rememberFingerprintDependencies( - `program-shard-merge-linux-v1\0${ld.cacheIdentity}\0${objcopy.cacheIdentity}`, - [ld.canonicalPath, objcopy.canonicalPath], - ); + : rememberFingerprintDependencies(`program-shard-merge-linux-v2\0${ld.cacheIdentity}`, [ + ld.canonicalPath, + ]); } const arch = driver.target.split("-", 1)[0]; if (arch !== "x86_64" && arch !== "aarch64") return null; diff --git a/packages/compiler/src/backend/object-localize.test.ts b/packages/compiler/src/backend/object-localize.test.ts index 9e4e302856..1a1e9d4bd4 100644 --- a/packages/compiler/src/backend/object-localize.test.ts +++ b/packages/compiler/src/backend/object-localize.test.ts @@ -28,7 +28,10 @@ const SHF_EXECINSTR = 0x4n; const SHF_GROUP = 0x200n; const STB_GLOBAL = 1; const STB_WEAK = 2; +const SHT_SYMTAB_SHNDX = 18; +const SHN_LORESERVE = 0xff00; const SHN_COMMON = 0xfff2; +const SHN_XINDEX = 0xffff; interface ElfSymbolSpec { name: string; @@ -53,7 +56,11 @@ function buildStrtab(names: string[]): { data: Uint8Array; offsets: Map s.name).filter((n) => n !== "")); const symData = new Uint8Array((symbols.length + 1) * 24); const symView = new DataView(symData.buffer); + const shndxData = new Uint8Array((symbols.length + 1) * 4); + let usesShndx = false; symbols.forEach((sym, i) => { const at = (i + 1) * 24; symView.setUint32(at, sym.name === "" ? 0 : strtab.offsets.get(sym.name)!, true); symView.setUint8(at + 4, (sym.binding << 4) | 0); - symView.setUint16(at + 6, sym.shndx, true); + if (sym.shndx >= SHN_LORESERVE && sym.shndx !== SHN_COMMON) { + symView.setUint16(at + 6, SHN_XINDEX, true); + new DataView(shndxData.buffer).setUint32((i + 1) * 4, sym.shndx, true); + usesShndx = true; + } else { + symView.setUint16(at + 6, sym.shndx, true); + } symView.setBigUint64(at + 8, sym.value ?? 0n, true); }); const all: ElfSectionSpec[] = [ @@ -82,9 +97,22 @@ function buildElf( }, { name: ".strtab", type: SHT_STRTAB, data: strtab.data }, { name: ".shstrtab", type: SHT_STRTAB }, + ...(usesShndx + ? [ + { + name: ".symtab_shndx", + type: SHT_SYMTAB_SHNDX, + data: shndxData, + link: sections.length + 1, + entsize: 4n, + }, + ] + : []), ]; + const shstrndx = sections.length + 3; + const extended = all.length >= SHN_LORESERVE; const shstr = buildStrtab(all.map((s) => s.name).filter((n) => n !== "")); - all[all.length - 1]!.data = shstr.data; + all[shstrndx]!.data = shstr.data; let offset = 64; const offsets = all.map((s) => { const data = s.data ?? new Uint8Array(0); @@ -106,8 +134,8 @@ function buildElf( view.setBigUint64(40, BigInt(shoff), true); view.setUint16(52, 64, true); view.setUint16(58, 64, true); - view.setUint16(60, all.length, true); - view.setUint16(62, all.length - 1, true); + view.setUint16(60, extended ? 0 : all.length, true); + view.setUint16(62, shstrndx >= SHN_LORESERVE ? SHN_XINDEX : shstrndx, true); all.forEach((s, i) => { const at = shoff + i * 64; const data = s.data ?? new Uint8Array(0); @@ -116,8 +144,8 @@ function buildElf( view.setUint32(at + 4, s.type, true); view.setBigUint64(at + 8, s.flags ?? 0n, true); view.setBigUint64(at + 24, BigInt(s.type === 0 ? 0 : offsets[i]!), true); - view.setBigUint64(at + 32, BigInt(data.length), true); - view.setUint32(at + 40, s.link ?? 0, true); + view.setBigUint64(at + 32, BigInt(i === 0 && extended ? all.length : data.length), true); + view.setUint32(at + 40, i === 0 && shstrndx >= SHN_LORESERVE ? shstrndx : (s.link ?? 0), true); view.setUint32(at + 44, s.info ?? 0, true); view.setBigUint64(at + 48, s.addralign ?? 1n, true); view.setBigUint64(at + 56, s.entsize ?? 0n, true); @@ -142,8 +170,10 @@ interface ReadElf { function readElf(bytes: Uint8Array): ReadElf { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); const shoff = Number(view.getBigUint64(40, true)); - const shnum = view.getUint16(60, true); - const shstrndx = view.getUint16(62, true); + let shnum = view.getUint16(60, true); + let shstrndx = view.getUint16(62, true); + if (shnum === 0) shnum = Number(view.getBigUint64(shoff + 32, true)); + if (shstrndx === SHN_XINDEX) shstrndx = view.getUint32(shoff + 40, true); const raw = [] as { nameOff: number; type: number; @@ -182,12 +212,16 @@ function readElf(bytes: Uint8Array): ReadElf { const symtabIdx = raw.findIndex((s) => s.type === SHT_SYMTAB); const symtab = raw[symtabIdx]!; const strtab = raw[symtab.link]!; + const shndxTable = raw.find((s) => s.type === SHT_SYMTAB_SHNDX && s.link === symtabIdx); const symbols = [] as ReadElf["symbols"]; for (let at = symtab.offset; at < symtab.offset + symtab.size; at += 24) { + let shndx = view.getUint16(at + 6, true); + if (shndx === SHN_XINDEX) + shndx = view.getUint32(shndxTable!.offset + ((at - symtab.offset) / 24) * 4, true); symbols.push({ name: name(strtab, view.getUint32(at, true)), binding: view.getUint8(at + 4) >> 4, - shndx: view.getUint16(at + 6, true), + shndx, }); } return { sections, symbols, symtabInfo: symtab.info }; @@ -241,6 +275,65 @@ describe("localizeElfObject", () => { expect(info & 0xffffffffn).toBe(2n); }); + test("keeps extended section numbering when the object has SHN_LORESERVE or more sections", () => { + // A sanitized dev program merges into an object with more sections than + // e_shnum and st_shndx can index: section 0 carries the count and the + // section-name table's index, and .symtab_shndx the symbols' indices. + const count = SHN_LORESERVE + 400; + const texts: ElfSectionSpec[] = Array.from({ length: count }, (_, i) => ({ + name: `.text.f${i}`, + type: SHT_PROGBITS, + flags: SHF_ALLOC | SHF_EXECINSTR, + data: new Uint8Array(4), + })); + const rela = new Uint8Array(24); + new DataView(rela.buffer).setBigUint64(8, (2n << 32n) | 2n, true); // sym 2 (high) + const object = buildElf( + [ + ...texts, + { + name: ".rela.text.last", + type: SHT_RELA, + data: rela, + link: count + 2, + info: count, + entsize: 24n, + }, + ], + [ + { name: "low", binding: STB_GLOBAL, shndx: 1 }, + { name: "high", binding: STB_GLOBAL, shndx: count }, + { name: "main", binding: STB_GLOBAL, shndx: count - 1 }, + { name: "libc_ref", binding: STB_GLOBAL, shndx: 0 }, + { name: "shared_common", binding: STB_GLOBAL, shndx: SHN_COMMON, value: 8n }, + ], + 0, + ); + const output = localizeElfObject(object, new Set(["main"])); + const header = new DataView(output.buffer, output.byteOffset, output.byteLength); + expect(header.getUint16(60, true)).toBe(0); + expect(header.getUint16(62, true)).toBe(SHN_XINDEX); + const localized = readElf(output); + expect(localized.sections.length).toBe(count + 6); + expect(localized.sections.filter((s) => s.type === SHT_SYMTAB_SHNDX)).toHaveLength(1); + const byName = new Map(localized.symbols.map((s) => [s.name, s])); + expect(byName.get("low")!.binding).toBe(0); + expect(byName.get("high")!.binding).toBe(0); + expect(localized.sections[byName.get("high")!.shndx]!.name).toBe(`.text.f${count - 1}`); + expect(byName.get("main")!.binding).toBe(STB_GLOBAL); + expect(localized.sections[byName.get("main")!.shndx]!.name).toBe(`.text.f${count - 2}`); + expect(byName.get("libc_ref")!.shndx).toBe(0); + expect(byName.get("shared_common")!.shndx).toBe(SHN_COMMON); + const relaOut = localized.sections.find((s) => s.name === ".rela.text.last")!; + expect(localized.sections[relaOut.info]!.name).toBe(`.text.f${count - 1}`); + const info = new DataView(relaOut.data.buffer, relaOut.data.byteOffset).getBigUint64(8, true); + expect(localized.symbols[Number(info >> 32n)]!.name).toBe("high"); + // The output reads back as the same object. + expect(readElf(localizeElfObject(output, new Set(["main"]))).symbols).toEqual( + localized.symbols, + ); + }); + test("resolves section groups: SHT_GROUP dropped, SHF_GROUP cleared, members and indices intact", () => { const group = new Uint8Array(8); const groupView = new DataView(group.buffer); diff --git a/packages/compiler/src/backend/object-localize.ts b/packages/compiler/src/backend/object-localize.ts index 302027484b..fb91ea85b6 100644 --- a/packages/compiler/src/backend/object-localize.ts +++ b/packages/compiler/src/backend/object-localize.ts @@ -84,9 +84,13 @@ interface ElfSymbol { name: string; info: number; other: number; - /** Resolved section index (SHT_SYMTAB_SHNDX applied), or a reserved - * SHN_* value below/at SHN_LORESERVE semantics. */ + /** Section index (SHT_SYMTAB_SHNDX applied), or the reserved SHN_* value + * when `reserved`. */ shndx: number; + /** st_shndx holds a reserved value (SHN_ABS, SHN_COMMON, ...), not a + * section index. An index from SHT_SYMTAB_SHNDX can itself be at or above + * SHN_LORESERVE. */ + reserved: boolean; } /** Demote every defined global/weak symbol not named in `keep` to a local @@ -143,8 +147,8 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) if (strtab === undefined) fail("ELF symbol table names an invalid string table"); // SHT_SYMTAB_SHNDX (only present when section indices overflow st_shndx): - // resolve the real indices up front and drop the section — the rebuilt - // object's section count only shrinks, so inline indices always fit. + // resolve the real indices up front and drop the section. The rebuilt + // object writes a new one when its own section count still overflows. const shndxSection = sections.find( (s) => s.type === SHT_SYMTAB_SHNDX && sections[s.link] === symtab, ); @@ -170,9 +174,11 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) for (let i = 0; i < symCount; i++) { const at = i * 24; let shndx: number = symView.getUint16(at + 6, true); + let reserved = shndx >= SHN_LORESERVE; if (shndx === SHN_XINDEX) { if (shndxView === null) fail("ELF symbol uses SHN_XINDEX without a SHT_SYMTAB_SHNDX table"); shndx = shndxView.getUint32(i * 4, true); + reserved = false; } symbols.push({ nameOffset: symView.getUint32(at, true), @@ -180,6 +186,7 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) info: symView.getUint8(at + 4), other: symView.getUint8(at + 5), shndx, + reserved, }); } @@ -212,11 +219,11 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) const symbolKept: boolean[] = []; for (let i = 0; i < symCount; i++) { const sym = symbols[i]!; - const regular = sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE; + const regular = !sym.reserved && sym.shndx !== SHN_UNDEF; symbolKept.push(!(regular && sectionKept[sym.shndx] !== true)); if (i === 0) continue; const binding = sym.info >> 4; - const defined = sym.shndx !== SHN_UNDEF && sym.shndx !== SHN_COMMON; + const defined = sym.shndx !== SHN_UNDEF && !(sym.reserved && sym.shndx === SHN_COMMON); if (binding !== STB_LOCAL && defined && !keep.has(sym.name)) { sym.info = (STB_LOCAL << 4) | (sym.info & 0xf); } @@ -236,6 +243,16 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) symbolMap[oldIndex] = newIndex; }); + // Extended section numbering: with SHN_LORESERVE or more sections, an + // index from SHN_LORESERVE up does not fit st_shndx, e_shnum or + // e_shstrndx. Such a symbol's st_shndx is SHN_XINDEX and its index goes in + // a SHT_SYMTAB_SHNDX section (one entry per symbol, appended after the + // kept sections); the counts go in section 0 (sh_size, sh_link). + let keptCount = 0; + for (const keptSection of sectionKept) if (keptSection) keptCount++; + const extended = keptCount >= SHN_LORESERVE; + const shndxOut = extended ? new Uint8Array(symbolOrder.length * 4) : null; + const shndxOutView = shndxOut === null ? null : new DataView(shndxOut.buffer); const newSymtabData = new Uint8Array(symbolOrder.length * 24); const newSymtabView = new DataView(newSymtabData.buffer); symbolOrder.forEach((oldIndex, newIndex) => { @@ -244,9 +261,14 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) newSymtabView.setUint32(at, sym.nameOffset, true); newSymtabView.setUint8(at + 4, sym.info); newSymtabView.setUint8(at + 5, sym.other); - const shndx = - sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE ? sectionMap[sym.shndx]! : sym.shndx; - newSymtabView.setUint16(at + 6, shndx, true); + const regular = !sym.reserved && sym.shndx !== SHN_UNDEF; + const shndx = regular ? sectionMap[sym.shndx]! : sym.shndx; + if (regular && shndx >= SHN_LORESERVE) { + newSymtabView.setUint16(at + 6, SHN_XINDEX, true); + shndxOutView!.setUint32(newIndex * 4, shndx, true); + } else { + newSymtabView.setUint16(at + 6, shndx, true); + } newSymtabView.setBigUint64(at + 8, symView.getBigUint64(oldIndex * 24 + 8, true), true); newSymtabView.setBigUint64(at + 16, symView.getBigUint64(oldIndex * 24 + 16, true), true); }); @@ -278,6 +300,33 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) // Rebuild. sh_link is a section index in every use this object can carry; // sh_info is one only on relocation sections. const kept = sections.filter((_, i) => sectionKept[i] === true); + const newSymtabIndex = sectionMap[symtabIndex]!; + if (shndxOut !== null) { + // The extended index table goes last, named in the section-name table. + const shstrtab = sections[shstrndx]!; + let nameOffset = shndxSection?.nameOffset; + if (nameOffset === undefined) { + const name = textEncoder.encode(".symtab_shndx\0"); + const grown = new Uint8Array(shstrtab.data.length + name.length); + grown.set(shstrtab.data); + grown.set(name, shstrtab.data.length); + nameOffset = shstrtab.data.length; + shstrtab.data = grown; + } + kept.push({ + nameOffset, + type: SHT_SYMTAB_SHNDX, + flags: 0n, + addr: 0n, + size: BigInt(shndxOut.length), + link: symtabIndex, + info: 0, + addralign: 4n, + entsize: 4n, + data: shndxOut, + }); + } + const newShstrndx = sectionMap[shstrndx]!; let offset = 64; const offsets: number[] = []; for (const section of kept) { @@ -301,8 +350,8 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) outView.setBigUint64(40, BigInt(shoffOut), true); outView.setUint16(54, 0, true); // e_phentsize outView.setUint16(56, 0, true); // e_phnum - outView.setUint16(60, kept.length, true); - outView.setUint16(62, sectionMap[shstrndx]!, true); + outView.setUint16(60, extended ? 0 : kept.length, true); + outView.setUint16(62, newShstrndx >= SHN_LORESERVE ? SHN_XINDEX : newShstrndx, true); kept.forEach((section, i) => { if (section.type !== SHT_NULL && section.type !== SHT_NOBITS) { out.set(section.data, offsets[i]!); @@ -313,12 +362,27 @@ export function localizeElfObject(object: Uint8Array, keep: ReadonlySet) outView.setBigUint64(at + 8, section.flags, true); outView.setBigUint64(at + 16, section.addr, true); outView.setBigUint64(at + 24, BigInt(offsets[i]!), true); + if (i === 0) { + // Section 0 carries the counts that overflow the ELF header. + outView.setBigUint64(at + 32, extended ? BigInt(kept.length) : 0n, true); + outView.setUint32(at + 40, newShstrndx >= SHN_LORESERVE ? newShstrndx : 0, true); + outView.setUint32(at + 44, 0, true); + return; + } outView.setBigUint64( at + 32, section.type === SHT_NOBITS ? section.size : BigInt(section.data.length), true, ); - outView.setUint32(at + 40, section.link === 0 ? 0 : (sectionMap[section.link] ?? 0), true); + outView.setUint32( + at + 40, + section.type === SHT_SYMTAB_SHNDX + ? newSymtabIndex + : section.link === 0 + ? 0 + : (sectionMap[section.link] ?? 0), + true, + ); outView.setUint32( at + 44, section.type === SHT_RELA || section.type === SHT_REL From 42e5d28684a36392f3906ff3bfa0a15ffa169ee2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:05:10 -0500 Subject: [PATCH 30/49] Read undeclared literal keys as undefined and keep filled-in JavaScript vars dynamic --- .../lowering/expressions/element-access.ts | 18 ++++++- .../src/frontend/lowering/lower-exprs.ts | 17 ++++++- .../src/frontend/lowering/lower-stmts.ts | 36 ++++++++++++++ .../corpus/var-object-filled-later.js.json | 6 +++ .../types-keyed-construction/main.ts.json | 6 +++ .../types-undeclared-quoted-key/main.ts.json | 6 +++ tests/corpus/var-object-filled-later.js | 48 +++++++++++++++++++ .../cases/types-keyed-construction/main.ts | 12 +++++ .../node_modules/@types/profilekit/index.d.ts | 8 ++++ .../@types/profilekit/package.json | 1 + .../node_modules/profilekit/index.js | 12 +++++ .../node_modules/profilekit/package.json | 1 + .../types-keyed-construction/package.json | 1 + .../cases/types-undeclared-quoted-key/main.ts | 20 ++++++++ .../node_modules/hostilekit/index.d.ts | 2 + .../node_modules/hostilekit/index.js | 5 ++ .../node_modules/hostilekit/package.json | 1 + .../types-undeclared-quoted-key/package.json | 1 + .../var-object-filled-later.js.json | 1 + 19 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/var-object-filled-later.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-keyed-construction/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts.json create mode 100644 tests/corpus/var-object-filled-later.js create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/main.ts create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/index.d.ts create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/package.json create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/index.js create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/package.json create mode 100644 tests/fixtures/npm/cases/types-keyed-construction/package.json create mode 100644 tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts create mode 100644 tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.d.ts create mode 100644 tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.js create mode 100644 tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/package.json create mode 100644 tests/fixtures/npm/cases/types-undeclared-quoted-key/package.json create mode 100644 tests/harness/corpus-costs/var-object-filled-later.js.json diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 29f249282f..8e60b33290 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -1,4 +1,5 @@ /* Element reads and writes (`a[i]`, `a[i] = v`), compound element updates, and keyed record reads. */ +import { OBJECT_PROTOTYPE_MEMBERS } from "../../shape-inference/domain.js"; import * as ts from "../../ts7/adapter.js"; import { lowerOptionalIndex, @@ -1319,6 +1320,21 @@ export function lowerRecordKeyRead( }; return lowerer.maybeNarrow(get, expr); } + // A literal key the shape does not declare, on a shape without an index + // signature, names no own property of any of its values: the read + // answers undefined, as Node's does. The checker's key check cannot vouch + // for it here, since the receiver can be typed from a package's + // JavaScript while the checker read its declarations. Object.prototype + // members keep the keyed read below. + if (shape.indexValue === undefined && !OBJECT_PROTOTYPE_MEMBERS.has(litKey)) { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: obj, loc }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }; + } } let key = litKey !== null @@ -1489,7 +1505,7 @@ function lowerRecordPropertyKey(lowerer: Lowerer, key: IrExpr, node: ts.Expressi * exactly the key JS derives; negative/computed keys stay runtime * expressions and canonicalize through ensureString instead). Null for * everything else. */ -function recordKeyLiteralText(node: ts.Expression): string | null { +export function recordKeyLiteralText(node: ts.Expression): string | null { if (ts.isStringLiteral(node)) return node.text; if (ts.isNumericLiteral(node)) return String(Number(node.text)); return null; diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 006975085b..984775e653 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -96,7 +96,8 @@ import { lowerUnionKeyedRead, } from "./expressions/property-access.js"; import { lowerNullishPair } from "./expressions/binary.js"; -import { lowerElementCompoundParts } from "./expressions/element-access.js"; +import { lowerElementCompoundParts, recordKeyLiteralText } from "./expressions/element-access.js"; +import { OBJECT_PROTOTYPE_MEMBERS } from "../shape-inference/domain.js"; export { DEFERRED_READ_MESSAGE, abstractPropertyDeclOf, @@ -1964,6 +1965,20 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr if (shape && !shape.tuple && shape.indexValue && shape.indexValue.kind !== "dyn") { return lowerer.lowerRecordKeyRead(expr, receiverT.shapeId, shape, true); } + // A literal key the shape does not declare is absent from every value + // (lowerRecordKeyRead answers undefined), whatever type the checker gave + // the read. + const literal = recordKeyLiteralText(expr.argumentExpression); + if ( + shape && + !shape.tuple && + !shape.indexValue && + literal !== null && + !OBJECT_PROTOTYPE_MEMBERS.has(literal) && + !shape.fields.some((field) => field.name === literal) + ) { + return lowerer.lowerRecordKeyRead(expr, receiverT.shapeId, shape, true); + } return null; } // `m[2]` on a JavaScript `RegExpExecArray | null` that a previous diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 25cce9cbf9..c037314649 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1318,6 +1318,7 @@ function jsDeclarationExpandsObject( inferred !== null && jsOpenObjectType(decl, inferred, lowerer.shapes, lowerer.unions).kind === "dyn") || arms.some((arm) => arm.kind === "record" && jsObjectBindingExpands(lowerer, decl, arm)) || + arms.some((arm) => arm.kind === "record" && jsObjectLiteralFilledLater(lowerer, decl, arm)) || (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && @@ -6553,6 +6554,41 @@ function lowerVariableInitializer( } } +/** A JS object literal that leaves out a required field of the binding's + * inferred record: tsc types `var p = {}; p.id = id;` by the members later + * assignments add, so the object only gains them after its initializer, and + * until then it is not a value of that record. Keep it a checked-dynamic + * object, as an object that gains keys outside the record is. */ +function jsObjectLiteralFilledLater( + lowerer: Lowerer, + decl: ts.VariableDeclaration, + type: IrType & { kind: "record" }, +): boolean { + if (decl.type || !decl.initializer || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return false; + let initializer = decl.initializer; + while (ts.isParenthesizedExpression(initializer)) initializer = initializer.expression; + if (!ts.isObjectLiteralExpression(initializer) || hasJsTypeAnnotation(decl)) return false; + const shape = lowerer.shapes.get(type.shapeId); + if (!shape || shape.tuple) return false; + const given = new Set(); + for (const property of initializer.properties.nodes) { + if (ts.isSpreadAssignment(property)) return false; + const name = property.name; + if (name === undefined) return false; + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) + given.add(name.text); + else return false; + } + // A field whose type holds undefined reads as it would before the write. + const holdsUndefined = (t: IrType): boolean => + t.kind === "dyn" || + t.kind === "undefinedT" || + (t.kind === "union" && lowerer.armTag(t.unionId, UNDEFINED_T) >= 0); + return shape.fields.some( + (field) => !given.has(field.name) && !field.name.startsWith("%") && !holdsUndefined(field.type), + ); +} + /** A JS object literal's inferred fields do not close its later storage. * Keep the original native object when writes add keys or delete fields; * converting it to a fixed record would discard that behavior. */ diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/var-object-filled-later.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/var-object-filled-later.js.json new file mode 100644 index 0000000000..26329147fb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/var-object-filled-later.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/var-object-filled-later.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-keyed-construction/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-keyed-construction/main.ts.json new file mode 100644 index 0000000000..5817aa0408 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-keyed-construction/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/types-keyed-construction/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts.json new file mode 100644 index 0000000000..8c56898f2a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/var-object-filled-later.js b/tests/corpus/var-object-filled-later.js new file mode 100644 index 0000000000..009d8b54d2 --- /dev/null +++ b/tests/corpus/var-object-filled-later.js @@ -0,0 +1,48 @@ +// A function-scoped `var` initialized with an object literal that later +// assignments fill in: tsc types the binding by the members those +// assignments add, but the object only gains them after its initializer. +"use strict"; +function load(id) { + var p = {}; + console.log(p.count, "count" in p, Object.keys(p).length); + p.id = id; + p.count = id * 2; + p.label = "item-" + id; + return p; +} +const p = load(7); +console.log(p.id + p.count, p.label.toUpperCase(), JSON.stringify(p), Object.keys(p).join(",")); + +function partial(name) { + var entry = { name: name }; + entry.size = name.length; + entry.tags = [name, name + "!"]; + return entry; +} +const e = partial("abc"); +console.log(e.size * 10, e.tags.join("|"), JSON.stringify(e)); + +function nested(depth) { + var node = {}; + node.depth = depth; + node.child = depth > 0 ? nested(depth - 1) : null; + return node; +} +console.log(JSON.stringify(nested(2))); + +function rows(n) { + var out = []; + for (var i = 0; i < n; i++) { + var row = {}; + row.index = i; + row.square = i * i; + out.push(row); + } + return out; +} +console.log(rows(4).map(function (r) { return r.index + ":" + r.square; }).join(" ")); + +var top = {}; +top.ready = true; +top.total = rows(3).reduce(function (s, r) { return s + r.square; }, 0); +console.log(top.ready, top.total, JSON.stringify(top)); diff --git a/tests/fixtures/npm/cases/types-keyed-construction/main.ts b/tests/fixtures/npm/cases/types-keyed-construction/main.ts new file mode 100644 index 0000000000..b163f3d1c6 --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/main.ts @@ -0,0 +1,12 @@ +// A plain-JavaScript package typed by an @types package builds its result +// key by key (`var p = {}; p.id = id; ...`): the object crosses the +// declaration boundary with every field, including the declared fields +// whose keys are not identifiers. +import { loadProfile, type Profile } from "profilekit"; + +const p: Profile = loadProfile(7); +console.log(p["a;b"].toUpperCase(), p["a;b"].length); +console.log(p["my-key"], p["my-key"].length, p["x y"].length); +console.log(p.id, p.count, JSON.stringify(p)); +const keys: string[] = Object.keys(p); +console.log(keys.join("|")); diff --git a/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/index.d.ts b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/index.d.ts new file mode 100644 index 0000000000..2da797940c --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/index.d.ts @@ -0,0 +1,8 @@ +export interface Profile { + id: number; + "a;b": string; + "my-key": string; + "x y": string; + count: number; +} +export declare function loadProfile(id: number): Profile; diff --git a/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/package.json b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/package.json new file mode 100644 index 0000000000..a7dd89b118 --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/@types/profilekit/package.json @@ -0,0 +1 @@ +{ "name": "@types/profilekit", "version": "1.0.0", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/index.js b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/index.js new file mode 100644 index 0000000000..8d8df93365 --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/index.js @@ -0,0 +1,12 @@ +"use strict"; +// Plain JavaScript: the profile is built key by key. +function loadProfile(id) { + var p = {}; + p.id = id; + p["a;b"] = "semi" + id; + p["my-key"] = "dash"; + p["x y"] = "space"; + p.count = id * 2; + return p; +} +exports.loadProfile = loadProfile; diff --git a/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/package.json b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/package.json new file mode 100644 index 0000000000..340a0a6c56 --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/node_modules/profilekit/package.json @@ -0,0 +1 @@ +{ "name": "profilekit", "version": "1.0.0", "main": "index.js" } diff --git a/tests/fixtures/npm/cases/types-keyed-construction/package.json b/tests/fixtures/npm/cases/types-keyed-construction/package.json new file mode 100644 index 0000000000..24992c9539 --- /dev/null +++ b/tests/fixtures/npm/cases/types-keyed-construction/package.json @@ -0,0 +1 @@ +{ "name": "types-keyed-construction-case", "private": true, "type": "module" } diff --git a/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts b/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts new file mode 100644 index 0000000000..d069face61 --- /dev/null +++ b/tests/fixtures/npm/cases/types-undeclared-quoted-key/main.ts @@ -0,0 +1,20 @@ +// A package typed from its JavaScript returns profiles without a key its +// declarations promise ("a;b"). A literal-key read of that key answers +// undefined, as Node's does; it never reads another field. +import { loadProfiles } from "hostilekit"; + +const profiles = loadProfiles(); +for (const p of profiles) { + console.log(p.id, p["a;b"] === undefined, typeof p["a;b"], "a;b" in p); + try { + console.log(p["a;b"].toUpperCase()); + } catch (e) { + console.log("upper threw", e instanceof TypeError); + } + try { + console.log(p["a;b"].length); + } catch (e) { + console.log("length threw", e instanceof TypeError); + } + console.log(p.id.toFixed(1), JSON.stringify(p)); +} diff --git a/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.d.ts b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.d.ts new file mode 100644 index 0000000000..4bf22dfea0 --- /dev/null +++ b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.d.ts @@ -0,0 +1,2 @@ +export type Profile = { id: number; "a;b": string }; +export declare function loadProfiles(): Profile[]; diff --git a/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.js b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.js new file mode 100644 index 0000000000..3165012566 --- /dev/null +++ b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/index.js @@ -0,0 +1,5 @@ +"use strict"; +function loadProfiles() { + return [{ id: 1 }]; +} +exports.loadProfiles = loadProfiles; diff --git a/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/package.json b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/package.json new file mode 100644 index 0000000000..2fdbc5b41e --- /dev/null +++ b/tests/fixtures/npm/cases/types-undeclared-quoted-key/node_modules/hostilekit/package.json @@ -0,0 +1 @@ +{ "name": "hostilekit", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/types-undeclared-quoted-key/package.json b/tests/fixtures/npm/cases/types-undeclared-quoted-key/package.json new file mode 100644 index 0000000000..afdb3cf5eb --- /dev/null +++ b/tests/fixtures/npm/cases/types-undeclared-quoted-key/package.json @@ -0,0 +1 @@ +{ "name": "types-undeclared-quoted-key-case", "private": true, "type": "module" } diff --git a/tests/harness/corpus-costs/var-object-filled-later.js.json b/tests/harness/corpus-costs/var-object-filled-later.js.json new file mode 100644 index 0000000000..1e74b61e72 --- /dev/null +++ b/tests/harness/corpus-costs/var-object-filled-later.js.json @@ -0,0 +1 @@ +{ "plain": 3400 } From 2859472f3a42690d14b811adc2013e516e28e012 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:06:10 -0500 Subject: [PATCH 31/49] Withdraw failed assumptions and compare type arguments in canonEqual - A comparison records the pairs it assumes equal in order, and when it fails, withdraws every pair assumed since it began. Before, a failed attempt (one union arm against a non-matching arm) stayed assumed, and a later comparison of the same pair answered true. - Nominal objects carry their type arguments, normalized lazily, and compare them; canonKey prints them. Before, instantiations of one generic class compared equal by name, so a union of arrays of two instantiations lost an arm. --- packages/compiler/src/frontend/canon.ts | 53 ++++++++++++++++--- .../compiler/test/canon-normalize.test.ts | 24 ++++++++- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/canon.ts b/packages/compiler/src/frontend/canon.ts index 7c1ac30999..5d75ae8065 100644 --- a/packages/compiler/src/frontend/canon.ts +++ b/packages/compiler/src/frontend/canon.ts @@ -53,6 +53,8 @@ export type Canon = k: "obj"; /** Class or standard-library declaration name, when nominal. */ nominal?: string; + /** The type arguments of a nominal generic instantiation. */ + typeArgs: () => readonly Canon[]; alias?: AliasRef; props: () => readonly CanonProp[]; index: () => readonly CanonIndex[]; @@ -99,7 +101,12 @@ export function canonKey(c: Canon, depth = 0): string { case "union": return c.arms.map((a) => canonKey(a, depth + 1)).join(" | "); case "obj": { - if (c.nominal !== undefined) return c.nominal; + if (c.nominal !== undefined) { + const args = c.typeArgs(); + return args.length === 0 + ? c.nominal + : `${c.nominal}<${args.map((a) => canonKey(a, depth + 1)).join(", ")}>`; + } const props = c .props() .map((p) => `${p.name}${p.optional ? "?" : ""}: ${canonKey(p.type(), depth + 1)}`); @@ -119,21 +126,42 @@ export function canonKey(c: Canon, depth = 0): string { } } +/** The pairs a comparison takes as equal while it runs, and the order they + * were assumed in, so that a failed comparison withdraws what it assumed. */ +interface Assumptions { + pairs: Map>; + trail: [Canon, Canon][]; +} + /** Whether two canonical nodes are the same type, structurally: unlike * canonKey, every part is compared (signature parameters, index - * signatures, any depth). A pair met again while it is being compared is - * taken as equal, which closes the comparison of recursive types. */ + * signatures, type arguments, any depth). A pair met again while it is + * being compared is taken as equal, which closes the comparison of + * recursive types; when the comparison fails, the pairs assumed during it + * are withdrawn. */ export function canonEqual( a: Canon, b: Canon, - assumed: Map> = new Map(), + assumed: Assumptions = { pairs: new Map(), trail: [] }, ): boolean { if (a === b) return true; if (a.k !== b.k) return false; - let pairs = assumed.get(a); + let pairs = assumed.pairs.get(a); if (pairs?.has(b)) return true; - if (pairs === undefined) assumed.set(a, (pairs = new Set())); + if (pairs === undefined) assumed.pairs.set(a, (pairs = new Set())); + const mark = assumed.trail.length; pairs.add(b); + assumed.trail.push([a, b]); + const equal = sameCanon(a, b, assumed); + if (!equal) + while (assumed.trail.length > mark) { + const [x, y] = assumed.trail.pop()!; + assumed.pairs.get(x)!.delete(y); + } + return equal; +} + +function sameCanon(a: Canon, b: Canon, assumed: Assumptions): boolean { const eq = (x: Canon, y: Canon): boolean => canonEqual(x, y, assumed); const sigsEqual = (xs: readonly CanonSig[], ys: readonly CanonSig[]): boolean => xs.length === ys.length && @@ -191,7 +219,11 @@ export function canonEqual( case "obj": { const o = b as typeof a; if (a.nominal !== o.nominal) return false; - if (a.nominal !== undefined) return true; + if (a.nominal !== undefined) { + const ax = a.typeArgs(); + const ox = o.typeArgs(); + return ax.length === ox.length && ax.every((x, i) => eq(x, ox[i]!)); + } const ap = a.props(); const op = o.props(); if (ap.length !== op.length) return false; @@ -394,6 +426,7 @@ export class Normalizer { args: type.getAliasTypeArguments().map((a) => this.canon(a, env)), tsType: type, }; + let typeArgs: readonly Canon[] | undefined; let props: readonly CanonProp[] | undefined; let index: readonly CanonIndex[] | undefined; let call: readonly CanonSig[] | undefined; @@ -417,6 +450,12 @@ export class Normalizer { k: "obj", ...(nominal !== undefined ? { nominal } : {}), ...(alias !== undefined ? { alias } : {}), + typeArgs: () => + (typeArgs ??= + nominal !== undefined && + ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 + ? checker.getTypeArguments(type as ts.TypeReference).map((a) => this.canon(a, env)) + : []), props: () => (props ??= checker.getPropertiesOfType(type).map((p) => ({ name: ts.symbolName(p), diff --git a/packages/compiler/test/canon-normalize.test.ts b/packages/compiler/test/canon-normalize.test.ts index 2ec669c397..ac25924f3a 100644 --- a/packages/compiler/test/canon-normalize.test.ts +++ b/packages/compiler/test/canon-normalize.test.ts @@ -2,7 +2,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; -import { canonKey, Normalizer, type Canon } from "../src/frontend/canon.js"; +import { canonEqual, canonKey, Normalizer, type Canon } from "../src/frontend/canon.js"; import { loadProgram } from "../src/frontend/program-node.js"; import * as ts from "../src/frontend/ts7/adapter.js"; @@ -63,6 +63,8 @@ beforeAll(() => { export type Chain = [number, Chain] | null; // Optional and rest tuple elements. export type Loose = [string, number?, ...boolean[]]; + // Arrays of two instantiations of one generic class. + export type Maps = Map[] | Map[]; `, ); load = loadProgram(entry); @@ -237,6 +239,26 @@ describe("canon", () => { expect(rest?.k === "union" && rest.arms.includes(tuple!)).toBe(true); }); + test("a failed comparison withdraws the pairs it assumed equal", () => { + const array = (p: "number" | "string"): Canon => ({ + k: "array", + elem: { k: "prim", p }, + readonly: false, + }); + // number[] fails against string[] before it matches number[]; that + // attempt must not make string[] match later. + const a: Canon = { k: "union", arms: [array("number"), array("number")] }; + const b: Canon = { k: "union", arms: [array("string"), array("number")] }; + expect(canonEqual(a, b)).toBe(false); + expect(canonEqual(b, a)).toBe(false); + }); + + test("instantiations of one generic class stay distinct", () => { + const maps = new Normalizer(checker).canon(aliases.get("Maps")!); + expect(maps.k === "union" && maps.arms.length).toBe(2); + expect(key(maps)).toBe("Map[] | Map[]"); + }); + test("optional and rest tuple elements keep their flags", () => { expect(key(new Normalizer(checker).canon(aliases.get("Loose")!))).toBe( "[string, number | undefined?, ...boolean]", From 55c43d9f27717cf3b402a69c00645740a956f449 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:15:32 -0500 Subject: [PATCH 32/49] Guard every x86 intrinsic header in the toolchain probe, and declare mkdtemp on macOS The implicit-toolchain probe includes every <...> header the runtime and vendored sources name, each behind __has_include. clang ships its x86 intrinsic headers for every target, so on arm64 macOS __has_include finds (included by scr_bytes.c and scr_string.c), and including it there is an #error. The probe guarded only and by target, so on arm64 macOS it failed, native cache warming reported that it could not validate the compiler toolchain, and builds ran without the persistent cache. The probe now guards every x86 intrinsic header family (*mmintrin.h, , ) and by target. ts7-process.test.c defines _POSIX_C_SOURCE, which hides mkdtemp on macOS; it now also defines _DARWIN_C_SOURCE there, as host.c does. --- packages/compiler/native/ts7-process.test.c | 4 ++ .../src/backend/native-toolchain.test.ts | 41 +++++++++++++++++++ .../compiler/src/backend/native-toolchain.ts | 5 ++- .../backend/native/compiler-fingerprint.ts | 28 +++++++++---- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/compiler/native/ts7-process.test.c b/packages/compiler/native/ts7-process.test.c index b6d760abd1..85de63db16 100644 --- a/packages/compiler/native/ts7-process.test.c +++ b/packages/compiler/native/ts7-process.test.c @@ -1,6 +1,10 @@ #ifndef _WIN32 #define _POSIX_C_SOURCE 200809L #endif +/* macOS declares mkdtemp only with the Darwin extensions visible. */ +#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) +#define _DARWIN_C_SOURCE 1 +#endif #include "ts7-process.h" #include #include diff --git a/packages/compiler/src/backend/native-toolchain.test.ts b/packages/compiler/src/backend/native-toolchain.test.ts index f1bcde404e..0ac21e8533 100644 --- a/packages/compiler/src/backend/native-toolchain.test.ts +++ b/packages/compiler/src/backend/native-toolchain.test.ts @@ -24,6 +24,7 @@ import { compileLibArchive, executableSectionEliminationFlags, executableNativeEnvironmentFingerprint, + implicitDependencyIncludeDirective, implicitDependencyProbeIncludes, parseLinkTraceFiles, resolveBuildCacheRoot, @@ -624,6 +625,46 @@ test("implicit dependency seeds include separately compiled vendor system header expect(includes).toContain(""); }); +test.skipIf(clangExecutable === undefined)( + "the implicit toolchain probe guards target-specific compiler headers", + async () => { + // clang ships its x86 intrinsic headers (, ...) for every + // target, so __has_include finds them on arm64, where including them is + // an #error, and the same holds for the ARM headers on x86. The probe + // must guard them by target. Only the compiler's own freestanding + // headers are visible here, so the result does not depend on the host's + // C library. + const includes = (await implicitDependencyProbeIncludes(runtimeSrcDir())).filter( + (include) => + include !== "" && /intrin\.h>$|^$|^$/.test(include), + ); + expect(includes).toContain(""); + const resourceDir = execFileSync(clangExecutable!, ["-print-resource-dir"], { + encoding: "utf8", + }).trim(); + const source = includes.map(implicitDependencyIncludeDirective).join(""); + for (const target of ["aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"]) { + const result = spawnSync( + clangExecutable!, + [ + `--target=${target}`, + "-ffreestanding", + "-nostdinc", + "-isystem", + join(resourceDir, "include"), + "-fsyntax-only", + "-x", + "c", + "-", + ], + { input: source, encoding: "utf8" }, + ); + expect(result.stderr, target).toBe(""); + expect(result.status, target).toBe(0); + } + }, +); + test.skipIf( process.platform === "win32" || clangExecutable === undefined || diff --git a/packages/compiler/src/backend/native-toolchain.ts b/packages/compiler/src/backend/native-toolchain.ts index 05f1f71225..6c024c6a57 100644 --- a/packages/compiler/src/backend/native-toolchain.ts +++ b/packages/compiler/src/backend/native-toolchain.ts @@ -26,7 +26,10 @@ export { export { CcCompileError, subprocessFailureDetail } from "./native/process.js"; export { compileLibArchive } from "./native/library.js"; export { localizeLibraryObjects } from "./native/object-merge.js"; -export { implicitDependencyProbeIncludes } from "./native/compiler-fingerprint.js"; +export { + implicitDependencyIncludeDirective, + implicitDependencyProbeIncludes, +} from "./native/compiler-fingerprint.js"; export { parseLinkTraceFiles, nativeLinkerDependencyPaths } from "./native/linker-fingerprint.js"; export { clearCcCaches, stageRuntimeObjects } from "./native/runtime-objects.js"; export { diff --git a/packages/compiler/src/backend/native/compiler-fingerprint.ts b/packages/compiler/src/backend/native/compiler-fingerprint.ts index ec427ce0a0..6f92707f63 100644 --- a/packages/compiler/src/backend/native/compiler-fingerprint.ts +++ b/packages/compiler/src/backend/native/compiler-fingerprint.ts @@ -195,15 +195,25 @@ export async function implicitDependencyProbeIncludes(rtDir: string): Promise" || include === "" - ? " && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))" - : include === "" - ? " && defined(_WIN32)" - : include === "" || include === "" || include === "" - ? " && (defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64))" - : ""; +/** Compiler-provided x86 headers: clang ships them for every target, so + * __has_include finds them on arm64 too, where including them is an + * #error. Covers the SSE/AVX intrinsic families (, + * , ...), , and . */ +function x86OnlyHeader(include: string): boolean { + return include === "" || /^<(x86|x86gpr|[a-z]*mm)intrin\.h>$/.test(include); +} + +/** One guarded line of the implicit-toolchain probe source: the header is + * included only when the compiler can find it and it belongs to the target + * being probed. */ +export function implicitDependencyIncludeDirective(include: string): string { + const targetCondition = x86OnlyHeader(include) + ? " && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))" + : include === "" + ? " && defined(_WIN32)" + : include === "" || include === "" || include === "" + ? " && (defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64))" + : ""; return `#if __has_include(${include})${targetCondition}\n#include ${include}\n#endif\n`; } From 984737596642748c6aa2b68144f800dd449f867f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:22:35 -0500 Subject: [PATCH 33/49] Run registered classes' static initializers in place and keep retried statements' diagnostics - A deferred class with static field initializers is reached where its statement evaluates: its file's %init retries it there (retryDeferredAtDeclaration), so its initializers lower in place. A failed retry reports nothing, restores the types it degraded, and leaves the class deferred; no later flush registers it, so it reports at its reference instead of registering after its initializers' place. Before, a class a later reference registered never ran its static initializers. - A generic class that registers on retry registers the units of its static methods. Before, a call to one was an undeclared function (SC9001). - A statement that lowers again after degrading a type keeps the diagnostics of the function-local class bodies it lowered the first time: the retry reuses those bodies instead of lowering them again. - retriableDiags and failedTypes are a Set and a Map owned by the lowerer instead of weak collections, and the degraded types of a mode are copied without an empty-array fallback, which the self-hosted compiler could not lower. - The coverage test of a class rejected behind a receiver probe pins its refusal under --tier=static-only, as the diagnostics corpus does; under localized failure the class compiles. --- .../src/frontend/lowering/lower-classes.ts | 3 + .../src/frontend/lowering/lower-modules.ts | 70 +++++++++++----- .../compiler/src/frontend/lowering/lowerer.ts | 84 +++++++++++++++++-- .../test/localized-failure-retries.test.ts | 70 ++++++++++++++++ ...calized-failure-registered-statics.ts.json | 6 ++ .../localized-failure-registered-statics.ts | 46 ++++++++++ ...calized-failure-registered-statics.ts.json | 1 + tests/harness/coverage.test.ts | 7 +- 8 files changed, 258 insertions(+), 29 deletions(-) create mode 100644 packages/compiler/test/localized-failure-retries.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-registered-statics.ts.json create mode 100644 tests/corpus/localized-failure-registered-statics.ts create mode 100644 tests/harness/corpus-costs/localized-failure-registered-statics.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index e25c689b51..e5c7d590b2 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -6216,6 +6216,7 @@ function lowerClassValue( const context = newFnCtx(true, null, null, VOID); info.localClass.context = context; lowerer.fnStack.push(context); + const diagsBefore = lowerer.diags.length; try { const bodies = lowerClassMembers(lowerer, info); for (const body of bodies) info.localClass.bodies.set(body.name, body); @@ -6230,6 +6231,8 @@ function lowerClassValue( }); } info.localClass.ready = true; + // A retry of the enclosing statement reuses these bodies. + for (const d of lowerer.diags.slice(diagsBefore)) lowerer.loweredOnceDiags.add(d); lowerer.onExprClassCollected?.(info); } finally { lowerer.fnStack.pop(); diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 8aa3ddb2c3..57c58a7607 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -179,6 +179,17 @@ export interface AppendGraph { * the require() edges preflight resolved — a CommonJS file's only edges * (the compiled CommonJS package a workspace module loaded on demand * imports, and the sibling packages and files IT requires). */ +/** Whether a class declaration has a static field with an initializer, + * which Node runs where the class statement evaluates. */ +function hasStaticFieldInitializer(decl: ts.ClassDeclaration): boolean { + return decl.members.nodes.some( + (member) => + ts.isPropertyDeclaration(member) && + member.initializer !== undefined && + member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) === true, + ); +} + function appendedEdgesOf( program: ts.Program, graph: AppendGraph | undefined, @@ -3229,7 +3240,19 @@ export function lowerFileInit( // by position) — exactly when JS evaluates decorators, static // initializers, and blocks, so code reading an earlier module // binding sees its assigned value. - const statics = [...lowerer.classes.values()] + const runsStatics = (c: ClassInfo): boolean => + lowerer.classInitFlags.has(c.def.name) || + c.callableBase !== undefined || + c.def.baseValueGlobal !== undefined || + c.runtimeStatics !== undefined || + c.staticFields.length > 0 || + (c.staticBlocks?.length ?? 0) > 0 || + c.classDecorators !== undefined; + const statics: { + pos: number; + info: ClassInfo | null; + deferred?: { symbol: ts.Symbol; decl: ts.ClassDeclaration }; + }[] = [...lowerer.classes.values()] .filter( (c) => // Class DECLARATIONS only: expression classes run their static @@ -3241,15 +3264,30 @@ export function lowerFileInit( ts.isClassDeclaration(c.decl) && !c.mixinInstance && ts.getSourceFileOfNode(c.decl) === sf && - (lowerer.classInitFlags.has(c.def.name) || - c.callableBase !== undefined || - c.def.baseValueGlobal !== undefined || - c.runtimeStatics !== undefined || - c.staticFields.length > 0 || - (c.staticBlocks?.length ?? 0) > 0 || - c.classDecorators !== undefined), + runsStatics(c), ) .map((c) => ({ pos: ts.tokenStartOfNode(c.decl!), info: c })); + // Localized failure: a deferred class with static field initializers + // is reached at its statement, where Node runs them. Its place retries + // it (retryDeferredAtDeclaration), and once it has registered there or + // through an earlier flush, its initializers lower in place. + for (const symbol of lowerer.deferredCollectors.keys()) + for (const decl of symbol.declarations) + if ( + ts.isClassDeclaration(decl) && + ts.getSourceFileOfNode(decl) === sf && + hasStaticFieldInitializer(decl) + ) + statics.push({ pos: ts.tokenStartOfNode(decl), info: null, deferred: { symbol, decl } }); + const lowerStaticsAt = (entry: (typeof statics)[number]): IrStmt[] => { + let info = entry.info; + if (entry.deferred) { + if (!lowerer.retryDeferredAtDeclaration(entry.deferred.symbol)) return []; + info = lowerer.classes.get(lowerer.classNamer(entry.deferred.decl)) ?? null; + if (info === null || !runsStatics(info)) return []; + } + return withClassInitFlag(lowerer, info!, lowerer.lowerStaticFieldInits(info!)); + }; // Statics-bearing MIXIN instantiations whose call evaluates in THIS // file: their declaration-time code runs when the call does — the // top-level statement lower-mixins recorded. Insertion order is @@ -3270,13 +3308,7 @@ export function lowerFileInit( // declarations never share a kept statement's start, so `<=` // changes nothing for them. while (at < statics.length && statics[at]!.pos <= ts.tokenStartOfNode(stmt)) { - body.push( - ...withClassInitFlag( - lowerer, - statics[at]!.info, - lowerer.lowerStaticFieldInits(statics[at]!.info), - ), - ); + body.push(...lowerStaticsAt(statics[at]!)); at++; } // A cycle binding's declaration stays at its position: its @@ -3295,13 +3327,7 @@ export function lowerFileInit( body.push(...lowerer.pendingClassExprInits.splice(0), ...stmtIr); } while (at < statics.length) { - body.push( - ...withClassInitFlag( - lowerer, - statics[at]!.info, - lowerer.lowerStaticFieldInits(statics[at]!.info), - ), - ); + body.push(...lowerStaticsAt(statics[at]!)); at++; } for (const { at, g } of unionEntryInits) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index de84fa3a9f..3a855eb59c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3792,6 +3792,14 @@ export class Lowerer { /** Declarations retryDeferred registered, in order: run() lowers the * bodies it had skipped. */ readonly retriedDeclarations: ts.Node[] = []; + /** Diagnostics of lowering that a statement's retry does not repeat (a + * function-local class's member bodies, lowered once and kept): + * dropAttemptDiags keeps them. */ + readonly loweredOnceDiags = new Set(); + /** Deferred classes whose static field initializers' place in %init + * has been lowered (retryDeferredAtDeclaration): a later flush must not + * register them, since their initializers would never run. */ + readonly staticInitsPassed = new Set(); /** Deferred declarations' collections, for the flush retry * (retryDeferred). */ readonly deferredCollectors = new Map< @@ -3803,9 +3811,9 @@ export class Lowerer { liftDynFieldPolicy = false; /** Collection diagnostics that a flush retry may simply run again (a * derived class whose base had not registered yet). */ - readonly retriableDiags = new WeakSet(); + readonly retriableDiags = new Set(); /** The checker type behind each type failure badType committed. */ - readonly failedTypes = new WeakMap(); + readonly failedTypes = new Map(); /** True while collectProgram runs: resolution helpers must not flush * deferred diagnostics (collection itself resolves symbols — extends * clauses — and collection order must not decide what reports). */ @@ -3888,7 +3896,8 @@ export class Lowerer { ) { // The mode's options, sorted by name; the values derived from them follow. this.alreadyFlushed = mode.alreadyFlushed ?? new Set(); - for (const [id, degraded] of mode.degradedTypes ?? []) this.degradedTypes.set(id, degraded); + if (mode.degradedTypes) + for (const [id, degraded] of mode.degradedTypes) this.degradedTypes.set(id, degraded); this.priorRetriedSymbols = mode.retriedSymbols ?? new Set(); this.externalTypes = mode.externalTypes ?? new Map(); this.ffiBindingSymbols = mode.ffiBindingSymbols ?? null; @@ -8921,8 +8930,25 @@ export class Lowerer { metadataPriority.set(sig.name, [3, expressionMetadataOrder++]); } else if (ts.isClassDeclaration(decl)) { const info = this.classes.get(this.classNamer(decl)); - if (info && !info.generic && !units.has(`%${info.def.name}.constructor`)) - this.onExprClassCollected?.(info); + if (!info) return; + if (!info.generic) { + if (!units.has(`%${info.def.name}.constructor`)) this.onExprClassCollected?.(info); + return; + } + // A generic class FAMILY's only units are its statics; its + // instantiations lower in the instance drain. + if (!info.staticMethods) return; + for (const [name, entry] of info.staticMethods) { + const unit = `%${info.def.name}.static:${name}`; + if (units.has(unit)) continue; + units.set(unit, { + order: unitOrder++, + roots: functionRoots(entry.member), + lower: () => lowerStaticMethod(this, info, name), + }); + if (reachable.has(unit)) queue.push(unit); + metadataPriority.set(unit, [3, expressionMetadataOrder++]); + } } }; const onEdge = this.onEdge; @@ -9525,7 +9551,9 @@ export class Lowerer { * lowers again; flushed declaration diagnostics (`rejects`) stay, since * the retry will not flush them a second time. */ dropAttemptDiags(diagsBefore: number): void { - const kept = this.diags.slice(diagsBefore).filter((d) => d.rejects !== undefined); + const kept = this.diags + .slice(diagsBefore) + .filter((d) => d.rejects !== undefined || this.loweredOnceDiags.has(d)); this.diags.length = diagsBefore; this.diags.push(...kept); } @@ -12544,9 +12572,27 @@ export class Lowerer { * caller to commit. */ private retryDeferred(symbol: ts.Symbol, diags: readonly ScrDiagnostic[]): boolean { if (this.tierMode !== "auto") return false; + if (this.staticInitsPassed.has(symbol)) return false; const collector = this.deferredCollectors.get(symbol); if (collector === undefined) return false; const slots: DynamicSlot[] = []; + // The types this retry degraded (not those of retries its collections + // ran), for retryDeferredAtDeclaration to restore when it fails. + const degraded: number[] = []; + try { + return this.retryDeferredRounds(symbol, collector, diags, slots, degraded); + } finally { + this.retryDegraded = degraded; + } + } + + private retryDeferredRounds( + symbol: ts.Symbol, + collector: { symbolOf: () => ts.Symbol | undefined; collect: () => void }, + diags: readonly ScrDiagnostic[], + slots: DynamicSlot[], + degraded: number[], + ): boolean { let current = diags; for (let round = 0; round < MAX_DECLARATION_RETRIES; round++) { const failing: { diag: ScrDiagnostic; failure: TypeFailure; id: number }[] = []; @@ -12580,6 +12626,7 @@ export class Lowerer { if (failing.length === 0 && !(retriable && round === 0)) return false; for (const { diag, failure, id } of failing) { this.degradedTypes.set(id, { lenient: DYN, code: diag.code, reason: diag.message }); + degraded.push(id); slots.push({ loc: diag.loc, failure, code: diag.code, reason: diag.message }); } this.deferredDiags.delete(symbol); @@ -12609,6 +12656,31 @@ export class Lowerer { return false; } + private retryDegraded: number[] = []; + + /** A deferred class with static field initializers is reached where its + * statement evaluates: Node runs the initializers there, referenced or + * not. Its file's %init calls this at that place, which retries the class + * as a flush would, so the initializers lower in place. A failed retry + * reports nothing and leaves the class deferred as it was, and from here + * on no flush registers it (staticInitsPassed): it reports instead. + * Returns whether the class has registered. */ + retryDeferredAtDeclaration(symbol: ts.Symbol): boolean { + const diags = this.deferredDiags.get(symbol); + let registered = !this.deferredCollectors.has(symbol); + if (!registered && diags !== undefined) { + this.retryDegraded = []; + registered = this.retryDeferred(symbol, diags); + if (!registered) { + for (const id of this.retryDegraded) this.degradedTypes.delete(id); + this.deferredDiags.set(symbol, [...diags]); + } + this.retryDegraded = []; + } + this.staticInitsPassed.add(symbol); + return registered; + } + collectSignature(decl: ts.FunctionDeclaration): void { return collectSignature(this, decl); } diff --git a/packages/compiler/test/localized-failure-retries.test.ts b/packages/compiler/test/localized-failure-retries.test.ts new file mode 100644 index 0000000000..6bf1b1e23d --- /dev/null +++ b/packages/compiler/test/localized-failure-retries.test.ts @@ -0,0 +1,70 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile } from "../src/index.js"; + +async function compileSource(source: string) { + const directory = mkdtempSync(join(tmpdir(), "scriptc-localized-")); + try { + const entry = join(directory, "main.ts"); + writeFileSync(entry, source); + return await compile(entry, { + outDir: directory, + outPath: join(directory, "program.ir"), + outputKind: "ir", + }); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +// A statement that lowers again after a later declarator degrades a type +// reuses the member bodies of a function-local class it lowered the first +// time, so the diagnostics of those bodies still report. +test("a degrading retry keeps the diagnostics of a local class lowered before it", async () => { + const result = await compileSource(`type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; +function make(): number { + const K = class { + m(): number { + const a = [1, 2]; + (a as any).foo = 1; + return a.length; + } + }, + size = (cb: Callbacks): number => (cb instanceof Set ? cb.size : 0); + return new K().m() + size(new Set([1])); +} +console.log(make()); +`); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected the local class's diagnostic"); + expect(result.diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "SC1090", + message: expect.stringContaining("assignment to non-variables"), + }), + ]), + ); +}); + +// Without the failing statement, the same program compiles: the arrow's +// parameter is a dynamic slot. +test("the same program without the local class's failure compiles", async () => { + const result = await compileSource(`type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; +function make(): number { + const K = class { + m(): number { + return 2; + } + }, + size = (cb: Callbacks): number => (cb instanceof Set ? cb.size : 0); + return new K().m() + size(new Set([1])); +} +console.log(make()); +`); + expect(result.ok, JSON.stringify(result.ok ? [] : result.diagnostics)).toBe(true); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-registered-statics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-registered-statics.ts.json new file mode 100644 index 0000000000..b3d375ea63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-registered-statics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-registered-statics.ts" + ], + "diags": [] +} diff --git a/tests/corpus/localized-failure-registered-statics.ts b/tests/corpus/localized-failure-registered-statics.ts new file mode 100644 index 0000000000..9ca49d4f26 --- /dev/null +++ b/tests/corpus/localized-failure-registered-statics.ts @@ -0,0 +1,46 @@ +// Localized failure, stage L2: a class that registers with dynamic slots +// runs its static field initializers where its statement evaluates, and a +// generic class's static methods compile once it registers. +type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; + +function sideEffect(label: string): number { + console.log("static init", label); + return label.length; +} + +// Registers at its statement: the initializers run before the next line. +class Registry { + state: any = { n: 1 }; + static count = sideEffect("registry"); + static names: string[] = ["a", "b"]; + read(): number { + return this.state.n; + } +} + +console.log("after the class statement"); + +// Never referenced: its initializer runs all the same. +class Unused { + slot: any = null; + static ready = sideEffect("unused"); +} + +// A generic class whose static method has a dynamic parameter. +class Box { + value: T; + constructor(v: T) { + this.value = v; + } + static describe(target: Callbacks | undefined): string { + return target === undefined ? "none" : typeof target; + } + static label(): string { + return "box"; + } +} + +const r = new Registry(); +console.log(r.read(), Registry.count, Registry.names.join(",")); +console.log(Box.label(), Box.describe(undefined), Box.describe(new Set([1])), new Box(3).value); diff --git a/tests/harness/corpus-costs/localized-failure-registered-statics.ts.json b/tests/harness/corpus-costs/localized-failure-registered-statics.ts.json new file mode 100644 index 0000000000..1254aa6e43 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-registered-statics.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 002a84f003..f8fa0da038 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -587,13 +587,18 @@ test("runtime-optional refined unions report blockers without crashing", () => { }); test("a rejected class reached only through a receiver probe reports its own diagnostic once", () => { - const { coverage } = analyze(join(repoRoot, "tests/diagnostics/deferred-class-receiver/main.ts")); + const program = join(repoRoot, "tests/diagnostics/deferred-class-receiver/main.ts"); + // The class's refusal, as the diagnostics corpus pins it (--tier=static-only). + const { coverage } = analyze(program, { tier: "static-only" }); const messages = coverage.diagnostics.map((d) => d.message); expect( messages.filter((m) => m.startsWith("'MapIterator'-typed class fields")), ).toHaveLength(1); expect(messages.some((m) => m.startsWith("constructing through a class value"))).toBe(true); expect(coverage.unreached?.diagnostics ?? []).toEqual([]); + // Localized failure retries the class where the probe's statement commits + // the flush, with the field policy lifted, and it compiles. + expect(analyze(program).coverage.diagnostics).toEqual([]); }); test("settled generic rest-order fences count each source statement once", () => { From f2c53ff2f8def14f074780c91a674c2d81b5fa34 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:32:41 -0500 Subject: [PATCH 34/49] Build the bootstrap seed without the function layout The native bootstrap compares the seed's LLVM, which the Node-hosted compiler emits, with the LLVM the native compiler emits for itself. The seed's build applied the committed function layout and the native compiler has none, so the two listed the same functions in different orders. The layout only orders functions, so the seed is built without it. --- scripts/bootstrap-seed.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/bootstrap-seed.mjs b/scripts/bootstrap-seed.mjs index f0993f1260..fc9a297d8f 100644 --- a/scripts/bootstrap-seed.mjs +++ b/scripts/bootstrap-seed.mjs @@ -51,6 +51,10 @@ async function main() { ...process.env, SCRIPTC_NATIVE_EMIT_IR: "1", SCRIPTC_NATIVE_OPTIMIZATION: sanitize ? "dev" : "release", + // The bootstrap compares the seed's LLVM with the native compiler's + // own emission, which has no function layout. The layout only orders + // the functions, so the seed is built without it. + SCRIPTC_NATIVE_LAYOUT: "", }, }, ); From 801444fc3040e7800c5d797eeb5417fce4664512 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:37:53 -0500 Subject: [PATCH 35/49] Keep optional chains out of the undeclared-key absence probe A presence test of a literal key that a record shape does not declare read the receiver as the record and answered undefined. In an optional chain that conversion runs before the chain can short-circuit, so `missing?.["a;b"] === undefined` threw a TypeError for a missing value stored dynamically, where Node answers true. An optional read keeps the optional chain's lowering, which reads a nullish receiver as undefined and the undeclared key of a present one as undefined. --- .../src/frontend/lowering/lower-exprs.ts | 5 ++++- .../main.cjs.json | 18 ++++++++++++++++++ .../undeclared-key-optional-chain/main.cjs | 11 +++++++++++ .../node_modules/hostilekit/index.d.ts | 2 ++ .../node_modules/hostilekit/index.js | 5 +++++ .../node_modules/hostilekit/package.json | 1 + .../main.cjs.json | 1 + 7 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/undeclared-key-optional-chain/main.cjs.json create mode 100644 tests/corpus/undeclared-key-optional-chain/main.cjs create mode 100644 tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.d.ts create mode 100644 tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.js create mode 100644 tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/package.json create mode 100644 tests/harness/corpus-costs/undeclared-key-optional-chain/main.cjs.json diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 984775e653..ca8e0b1bcf 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1967,9 +1967,12 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr } // A literal key the shape does not declare is absent from every value // (lowerRecordKeyRead answers undefined), whatever type the checker gave - // the read. + // the read. An optional chain keeps its own lowering, which reads a + // nullish receiver as undefined instead of converting it. const literal = recordKeyLiteralText(expr.argumentExpression); if ( + !expr.questionDotToken && + !hasOptionalChainGuard(expr.expression) && shape && !shape.tuple && !shape.indexValue && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/undeclared-key-optional-chain/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/undeclared-key-optional-chain/main.cjs.json new file mode 100644 index 0000000000..252e5eb99d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/undeclared-key-optional-chain/main.cjs.json @@ -0,0 +1,18 @@ +{ + "order": [ + "/tests/corpus/undeclared-key-optional-chain/main.cjs" + ], + "diags": [ + { + "code": "SC1010", + "message": "the 'hostilekit' module is not supported yet", + "loc": { + "file": "/tests/corpus/undeclared-key-optional-chain/main.cjs", + "start": 289, + "end": 329 + }, + "milestone": "M4", + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + } + ] +} diff --git a/tests/corpus/undeclared-key-optional-chain/main.cjs b/tests/corpus/undeclared-key-optional-chain/main.cjs new file mode 100644 index 0000000000..de7b3351a1 --- /dev/null +++ b/tests/corpus/undeclared-key-optional-chain/main.cjs @@ -0,0 +1,11 @@ +// A package typed from its JavaScript returns profiles without a key its +// declarations promise ("a;b"). An optional read of that key answers +// undefined for a profile, and for a missing one (the chain reads the +// nullish receiver as undefined), in presence tests and in values. +const { loadProfiles } = require("hostilekit"); +const profiles = loadProfiles(); +const present = profiles[0]; +const missing = profiles[5]; +console.log(present?.["a;b"] === undefined, present?.["a;b"] ?? "none", typeof present?.["a;b"]); +console.log(missing?.["a;b"] === undefined, missing?.["a;b"] ?? "none", typeof missing?.["a;b"]); +console.log(present["a;b"] === undefined, present.id); diff --git a/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.d.ts b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.d.ts new file mode 100644 index 0000000000..4bf22dfea0 --- /dev/null +++ b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.d.ts @@ -0,0 +1,2 @@ +export type Profile = { id: number; "a;b": string }; +export declare function loadProfiles(): Profile[]; diff --git a/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.js b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.js new file mode 100644 index 0000000000..3165012566 --- /dev/null +++ b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/index.js @@ -0,0 +1,5 @@ +"use strict"; +function loadProfiles() { + return [{ id: 1 }]; +} +exports.loadProfiles = loadProfiles; diff --git a/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/package.json b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/package.json new file mode 100644 index 0000000000..2fdbc5b41e --- /dev/null +++ b/tests/corpus/undeclared-key-optional-chain/node_modules/hostilekit/package.json @@ -0,0 +1 @@ +{ "name": "hostilekit", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/harness/corpus-costs/undeclared-key-optional-chain/main.cjs.json b/tests/harness/corpus-costs/undeclared-key-optional-chain/main.cjs.json new file mode 100644 index 0000000000..8cfabb847b --- /dev/null +++ b/tests/harness/corpus-costs/undeclared-key-optional-chain/main.cjs.json @@ -0,0 +1 @@ +{ "plain": 3000 } From a8080733dff97efd5da28230a44033396e178d53 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:42:51 -0500 Subject: [PATCH 36/49] Build the bootstrap test's own seed without the function layout The bootstrap test builds its own seed when CI shares none, and that build still applied the committed function layout, so its LLVM listed functions in a different order from the native compiler's own emission. Both seed builds now share one environment, which leaves the layout out. --- scripts/bootstrap-seed.mjs | 23 +++++++++++-------- tests/harness/bootstrap-seed.test.ts | 18 ++++++++++++++- .../self-hosting-native-driver.test.ts | 9 ++------ 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/scripts/bootstrap-seed.mjs b/scripts/bootstrap-seed.mjs index fc9a297d8f..6bce075f5e 100644 --- a/scripts/bootstrap-seed.mjs +++ b/scripts/bootstrap-seed.mjs @@ -25,6 +25,19 @@ export function verifyBootstrapSeed(directory, revision, sanitize) { } } +/** The environment a bootstrap seed builds with, over the inherited one. The + * bootstrap compares the seed's LLVM with the native compiler's own emission, + * which has no function layout. The layout only orders the functions, so the + * seed is built without it. */ +export function seedBuildEnvironment(sanitize) { + return { + SCRIPTC_SAN: sanitize ? "1" : "", + SCRIPTC_NATIVE_EMIT_IR: "1", + SCRIPTC_NATIVE_OPTIMIZATION: sanitize ? "dev" : "release", + SCRIPTC_NATIVE_LAYOUT: "", + }; +} + async function main() { const directory = process.env.SCRIPTC_BOOTSTRAP_SEED_DIRECTORY; const revision = process.env.GITHUB_SHA; @@ -47,15 +60,7 @@ async function main() { { cwd: root, stdio: "inherit", - env: { - ...process.env, - SCRIPTC_NATIVE_EMIT_IR: "1", - SCRIPTC_NATIVE_OPTIMIZATION: sanitize ? "dev" : "release", - // The bootstrap compares the seed's LLVM with the native compiler's - // own emission, which has no function layout. The layout only orders - // the functions, so the seed is built without it. - SCRIPTC_NATIVE_LAYOUT: "", - }, + env: { ...process.env, ...seedBuildEnvironment(sanitize) }, }, ); writeFileSync( diff --git a/tests/harness/bootstrap-seed.test.ts b/tests/harness/bootstrap-seed.test.ts index 76b1f6de5a..2da27baea0 100644 --- a/tests/harness/bootstrap-seed.test.ts +++ b/tests/harness/bootstrap-seed.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { load } from "js-yaml"; import { expect, test } from "vitest"; -import { verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs"; +import { seedBuildEnvironment, verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs"; test("bootstrap artifacts require a complete seed from the same revision, lane, and path", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-seed-contract-")); @@ -36,6 +36,22 @@ test("bootstrap artifacts require a complete seed from the same revision, lane, } }); +test("seeds build without the function layout in both lanes, shared or not", () => { + for (const sanitize of [false, true]) + expect(seedBuildEnvironment(sanitize)).toEqual({ + SCRIPTC_SAN: sanitize ? "1" : "", + SCRIPTC_NATIVE_EMIT_IR: "1", + SCRIPTC_NATIVE_OPTIMIZATION: sanitize ? "dev" : "release", + SCRIPTC_NATIVE_LAYOUT: "", + }); + // The bootstrap test builds its own seed when CI shares none. + const driver = readFileSync( + new URL("./self-hosting-native-driver.test.ts", import.meta.url), + "utf8", + ); + expect(driver).toContain("...seedBuildEnvironment(sanitize)"); +}); + test("CI restores each seed only into its matching instrumented bootstrap lane", () => { const workflow = load( readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), diff --git a/tests/harness/self-hosting-native-driver.test.ts b/tests/harness/self-hosting-native-driver.test.ts index 1cc920bd2e..8cfd16eea3 100644 --- a/tests/harness/self-hosting-native-driver.test.ts +++ b/tests/harness/self-hosting-native-driver.test.ts @@ -1,4 +1,4 @@ -import { verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs"; +import { seedBuildEnvironment, verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs"; import { execFile, spawnSync } from "node:child_process"; import { chmodSync, @@ -89,12 +89,7 @@ test( // seed avoids optimizing the huge compiler module before ASan can // exercise its complete traversal, serialization and LLVM emission. ...options, - env: { - ...process.env, - SCRIPTC_SAN: sanitize ? "1" : "", - SCRIPTC_NATIVE_EMIT_IR: "1", - SCRIPTC_NATIVE_OPTIMIZATION: sanitize ? "dev" : "release", - }, + env: { ...process.env, ...seedBuildEnvironment(sanitize) }, }, ), ); From ef6f8838d0ebda35515d4651165e55527ef0286b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:53:54 -0500 Subject: [PATCH 37/49] Address review feedback --- .../src/backend/llvm/emit-statements.ts | 2 +- packages/compiler/src/backend/llvm/emitter.ts | 12 ++++++---- .../compiler/src/backend/llvm/expr-bytes.ts | 12 +++++----- .../test/counted-loop-emission.test.ts | 22 ++++++++++++++++++- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 310c5abb4f..66bbf34d0b 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2818,7 +2818,7 @@ function emitStripMinedCountedLoop( // iteration: its plain load or store is not merged with another // iteration's or moved out of the loop. const outer = emitter.plainSharedLoop; - emitter.plainSharedLoop = { counter: counter.localId, target }; + emitter.plainSharedLoop = { counter: counter.localId, target, depth: emitter.loopDepth }; try { emitter.emitBlock(s.body); } finally { diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 095515e314..6a058299e5 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1824,11 +1824,15 @@ export class LlEmitter { * guard proved thread-owned. */ bytesOwned: ReadonlySet = new Set(); /** Worker programs: the strip-mined counted loop whose body is being - * emitted (emitStripMinedCountedLoop), with its counter and its jump - * target. An element access of a view that may be shared, at the counter - * and directly in that loop's body, is a plain load or store + * emitted (emitStripMinedCountedLoop), with its counter, its jump target + * and its loopDepth. An element access of a view that may be shared, at + * the counter and directly in that loop's body, is a plain load or store * (plainSharedElement in expr-bytes.ts). */ - plainSharedLoop: { counter: string; target: LlEmitter["jumpTargets"][number] } | null = null; + plainSharedLoop: { + counter: string; + target: LlEmitter["jumpTargets"][number]; + depth: number; + } | null = null; /** The alias-scope attachment of each typed-array element read and write * in the current loop version's fast copy (emitVersionedLoop), and the * one the element access being emitted carries (bytesElementAlias). */ diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index 10363c19c5..2493a343d1 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -331,10 +331,11 @@ function emitBytesLoad( /** Worker programs: whether an element access at `index` of a view that * may be shared is a plain load or store, like a thread-owned view's. It is * when the index is the counter of the strip-mined counted loop whose body - * holds the access, outside any loop nested in that body: every iteration - * then reaches a different element, so LLVM cannot merge an element's reads - * across iterations or move them out of the loop, and the loop can be - * vectorized. A racing write from another thread can still be seen or + * holds the access, outside any loop nested in that body (its condition and + * update included: a nested loop that waits on the element reads it on + * every iteration, so the read stays atomic): every iteration then reaches + * a different element, so LLVM cannot merge an element's reads across + * iterations or move them out of the loop, and the loop can be vectorized. A racing write from another thread can still be seen or * missed within one iteration, the documented gap of non-atomic shared * accesses. Float16 elements keep their runtime path. */ function plainSharedElement( @@ -344,7 +345,8 @@ function plainSharedElement( ): boolean { const loop = host.plainSharedLoop; if (loop === null || elem === "f16" || index?.kind !== "varRef") return false; - if (index.localId !== loop.counter) return false; + // A nested loop raises loopDepth before its condition is emitted. + if (index.localId !== loop.counter || host.loopDepth !== loop.depth) return false; let innermost: (typeof host.jumpTargets)[number] | undefined; for (let i = host.jumpTargets.length - 1; i >= 0 && innermost === undefined; i--) if (host.jumpTargets[i]!.kind === "loop") innermost = host.jumpTargets[i]; diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index fe48deeeb5..e9a79a71ab 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -267,6 +267,17 @@ test("worker programs read and write shared elements at a strip-mined loop's cou " for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) t += a[i]! + a[j]!;", " return t;", "}", + "export function waits(a: Int32Array, out: Uint8Array, n: number): number {", + " let spins = 0;", + " for (let i = 0; i < n; i++) {", + " out[i] = 1;", + " while (a[i] === 0) spins++;", + " do spins++; while (a[i] === 1);", + " for (let k = 0; a[i]! > k; k++) spins++;", + " for (let k = 0; k < 3; k += a[i]!) spins++;", + " }", + " return spins;", + "}", "export function spin(a: Int32Array): number {", " let n = 0;", " while (a[0] === 0 && n < 10) n++;", @@ -279,7 +290,7 @@ test("worker programs read and write shared elements at a strip-mined loop's cou "codes(c);", "shift(c);", "const d = new Int32Array(new SharedArrayBuffer(16)).fill(1);", - "console.log(b[7], c[0], rows(d, 4), spin(d));", + "console.log(b[7], c[0], rows(d, 4), spin(d), waits(new Int32Array(new SharedArrayBuffer(16)).fill(2), new Uint8Array(new SharedArrayBuffer(4)), 4));", "if (!isMainThread) new Worker(new URL(import.meta.url));", ].join("\n"), ); @@ -319,6 +330,15 @@ test("worker programs read and write shared elements at a strip-mined loop's cou expect(rows).toMatch(/load atomic i32, ptr %/); // A loop that is not counted keeps atomic reads. expect(bodies("spin")).toMatch(/load atomic i32, ptr %/); + // A nested loop that tests the outer counter's element in its condition + // or steps by it reads it again on every iteration: each read stays + // atomic, so another thread's write ends the wait. + const waits = bodies("waits"); + expect(waits).toMatch(/^loop\.poll\d+:$/m); + expect(waits.match(/load atomic i32, ptr %/g)?.length).toBeGreaterThanOrEqual(4); + // The outer loop's write at its counter stays plain (speculation counters + // are atomic stores to globals). + expect(waits).not.toMatch(/store atomic i8 \S+, ptr %/); } finally { await rm(dir, { recursive: true, force: true }); } From d60698695f25b74619ba9562a853eacc6643e7f9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:56:15 -0500 Subject: [PATCH 38/49] Read an ignored element before its callback runs The element-independent callback paths read the slot after the callback: - filter appended the slot as it was after the predicate; - find and findLast read the element only once the predicate held. A predicate that overwrote or removed the current slot therefore kept or returned the new value, where JavaScript keeps the element it read before calling the predicate. An unfused filter over proven-present elements also stored the placeholder it passed to a predicate with an unused element parameter. - filter, find and findLast keep a snapshot of the slot, read before the callback runs (arraySlotSnapshot): the value, or the dynamic value of an element outside the element type. filter appends that snapshot as it was stored, and find returns it, with the checked conversion for such an element. Fused loops take the same snapshot before a stage whose callback ignores the element. - Leaving an ignored element unread now applies only to arrays that can hold elements outside their element type (elementMayBeForeign) and are not proven present. Every other array reads the element as before, so its filter stores the element it read. --- .../src/frontend/lowering/array-values.ts | 152 ++++++++++++----- .../lowering/containers/array-callbacks.ts | 156 +++++++++++++----- .../lowering/containers/array-pipelines.ts | 92 ++++++++--- .../corpus/8014-foreign-element-callbacks.ts | 58 +++++++ 4 files changed, 358 insertions(+), 100 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index a35cd2179d..8fbb77bae5 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -6,6 +6,7 @@ import { DYN, F64, type IrExpr, + type IrLocal, type IrStmt, type IrType, isIntegerBytesElem, @@ -460,22 +461,95 @@ export function unreadArgument(lowerer: Lowerer, type: IrType, loc: SrcLoc): IrE return null; } -/** Append the element at `arr[index]` to `out`, an array of the same - * element type, as it is stored: a value, a present undefined, or an - * element outside the element type (stored through a dynamic view), which - * `out` keeps as one, as the view's push stores it. `out` is a binding, - * and `value` names a local of the read's type (arrayValueType) that holds - * a stored value on its way. */ -export function arraySlotAppend( +/** The locals of an element snapshot (arraySlotSnapshot): whether the slot + * held an element outside the element type, the value read otherwise, and + * that element's dynamic value otherwise. */ +export interface SlotSnapshot { + isForeign: string; + value: string; + foreign: string; +} + +/** The locals a snapshot of an `elem` element uses. */ +export function slotSnapshotLocals(lowerer: Lowerer, elem: IrType, ids: SlotSnapshot): IrLocal[] { + return [ + { id: ids.isForeign, name: "foreign", type: BOOL, mutable: false }, + { id: ids.value, name: "value", type: arrayValueType(lowerer, elem), mutable: false }, + { id: ids.foreign, name: "element", type: DYN, mutable: false }, + ]; +} + +/** Read the element at `arr[index]` as it is stored, before a callback runs, + * as JavaScript reads it before calling a callback that ignores it: a value + * or a present undefined into `ids.value`, or an element outside the element + * type (stored through a dynamic view) as its dynamic value into + * `ids.foreign`, which `ids.isForeign` tells apart. */ +export function arraySlotSnapshot( lowerer: Lowerer, - out: IrExpr, arr: IrExpr, index: IrExpr, elem: IrType, - value: string, + ids: SlotSnapshot, + loc: SrcLoc, +): IrStmt[] { + const valueT = arrayValueType(lowerer, elem); + const isForeign: IrExpr = { kind: "varRef", localId: ids.isForeign, type: BOOL, loc }; + const missing = + valueT.kind === "dyn" ? dynUndefinedExpr(loc) : lowerer.wrappedUndefined(valueT, loc)!; + return [ + { + kind: "varDecl", + localId: ids.isForeign, + init: { + kind: "bin", + op: "===", + left: { kind: "arrayState", arr, index, type: F64, loc }, + right: { kind: "numLit", value: 3, type: F64, loc }, + type: BOOL, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: ids.value, + init: { + kind: "ternary", + cond: isForeign, + then: missing, + else_: arrayValueRead(lowerer, arr, index, elem, loc), + type: valueT, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: ids.foreign, + init: { + kind: "ternary", + cond: isForeign, + then: { kind: "libCall", fn: "array.foreign", args: [arr, index], type: DYN, loc }, + else_: dynUndefinedExpr(loc), + type: DYN, + loc, + }, + loc, + }, + ]; +} + +/** Append a snapshot (arraySlotSnapshot) of an element to `out`, an array of + * the same element type, as it was stored: an element outside the element + * type stays one, appended as the view's push appends it. `out` is a + * binding. */ +export function arraySnapshotAppend( + lowerer: Lowerer, + out: IrExpr, + elem: IrType, + ids: SlotSnapshot, loc: SrcLoc, ): IrStmt { - const valueT = arrayValueType(lowerer, elem); const end: IrExpr = { kind: "arrIntrinsic", method: "length", @@ -484,37 +558,15 @@ export function arraySlotAppend( type: F64, loc, }; - const plain: IrStmt = { - kind: "block", - body: [ - { - kind: "varDecl", - localId: value, - init: arrayValueRead(lowerer, arr, index, elem, loc), - loc, - }, - arrayValueStore( - lowerer, - out, - end, - { kind: "varRef", localId: value, type: valueT, loc }, - elem, - loc, - ), - ], + const value: IrExpr = { + kind: "varRef", + localId: ids.value, + type: arrayValueType(lowerer, elem), loc, }; - if (!elementMayBeForeign(lowerer, elem)) return plain; return { kind: "if", - cond: { - kind: "bin", - op: "===", - left: { kind: "arrayState", arr, index, type: F64, loc }, - right: { kind: "numLit", value: 3, type: F64, loc }, - type: BOOL, - loc, - }, + cond: { kind: "varRef", localId: ids.isForeign, type: BOOL, loc }, then: [ { kind: "exprStmt", @@ -523,14 +575,36 @@ export function arraySlotAppend( recv: { kind: "dynFrom", value: out, liveRef: true, type: DYN, loc }, method: "push", calleeName: "push", - args: [{ kind: "libCall", fn: "array.foreign", args: [arr, index], type: DYN, loc }], + args: [{ kind: "varRef", localId: ids.foreign, type: DYN, loc }], type: DYN, loc, }, loc, }, ], - else_: [plain], + else_: [arrayValueStore(lowerer, out, end, value, elem, loc)], + loc, + }; +} + +/** A snapshot (arraySlotSnapshot) as a value of `type`: the value read, or + * the element outside the element type through the checked conversion, + * which such an element fails as a typed read does. `typed` converts the + * value read. */ +export function arraySnapshotValue( + lowerer: Lowerer, + elem: IrType, + ids: SlotSnapshot, + type: IrType, + typed: (value: IrExpr) => IrExpr, + loc: SrcLoc, +): IrExpr { + return { + kind: "ternary", + cond: { kind: "varRef", localId: ids.isForeign, type: BOOL, loc }, + then: lowerer.coerceToExpected({ kind: "varRef", localId: ids.foreign, type: DYN, loc }, type), + else_: typed({ kind: "varRef", localId: ids.value, type: arrayValueType(lowerer, elem), loc }), + type, loc, }; } diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 77292389d8..497b50a27e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -38,7 +38,9 @@ import { } from "../lower-array-sort.js"; import { arrayIndexPresent, - arraySlotAppend, + arraySlotSnapshot, + arraySnapshotAppend, + arraySnapshotValue, arrayValueRead, arrayValueStore, arrayValueType, @@ -46,7 +48,9 @@ import { elementMayBeForeign, foreignElementThrow, primitiveElement, + slotSnapshotLocals, unreadArgument, + type SlotSnapshot, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -105,7 +109,10 @@ export function lowerArrayHofCall( const callback = booleanFilter ? { fnArg: truthyFilterCallback(lowerer, valueT, loc), arity: 1, unread: false } : lowerArrayCallback(lowerer, argNode, [valueT], arrayOf(elem)); - const { fnArg, arity, unread } = callback; + const { fnArg, arity } = callback; + // Only an array that can hold elements outside its element type needs to + // leave an element its callback ignores unread. + const unread = callback.unread && !present && elementMayBeForeign(lowerer, elem); const fnRet = fnArg.type.ret; if (method === "map" && (fnRet.kind === "void" || fnRet.kind === "func")) { // The result array U[] is unrepresentable here (no void elements; @@ -556,6 +563,10 @@ function callbackValueRead( : arrayValueRead(lowerer, arr, index, elem, loc); } +/** The snapshot locals of the filter and find helpers; `value` is their + * element local `v.0`. */ +const FILTER_SNAPSHOT: SlotSnapshot = { isForeign: "vf.0", value: "v.0", foreign: "vd.0" }; + /** Interned synthetic loop function for one (method, elem, fnRet, arity) * combo. Named `%arr..` ('%' keeps it out of the user * namespace); rides `liftedFns` into the module like a lifted lambda (it @@ -830,8 +841,20 @@ function buildArrayHofFn( elem, present, loc, - unread, ); + // What the callback takes: the element, or a placeholder for one it + // ignores (callbackValueRead's `unread`). + const argElem = unread + ? callbackValueRead( + lowerer, + varRef("a.0", arrT, loc), + varRef("i.0", F64, loc), + elem, + present, + loc, + true, + ) + : getElem; const hasElem: IrExpr = { kind: "arrayHas", arr: varRef("a.0", arrT, loc), @@ -880,7 +903,7 @@ function buildArrayHofFn( kind: "if", cond: hasElem, then: [ - { kind: "varDecl", localId: "mapped.0", init: callF(getElem), loc }, + { kind: "varDecl", localId: "mapped.0", init: callF(argElem), loc }, arrayValueStore( lowerer, varRef("out.0", outT, loc), @@ -1016,7 +1039,19 @@ function buildArrayHofFn( ); returnType = outT; const retained = filterRetainedValue(lowerer, varRef("v.0", valueT, loc), elem, outElem, loc); - const copied = (arity === 0 || unread) && !present && typeEquals(outElem, elem); + // A predicate that ignores the element of an array that can hold + // elements outside its element type keeps a snapshot of the slot, read + // before the predicate runs, as stored (arraySlotSnapshot). + const copied = + (arity === 0 || unread) && + !present && + typeEquals(outElem, elem) && + elementMayBeForeign(lowerer, elem); + // `v.0` is declared above; the snapshot adds its other two locals. + if (copied) + locals.push( + ...slotSnapshotLocals(lowerer, elem, FILTER_SNAPSHOT).filter((l) => l.id !== "v.0"), + ); body = [ { kind: "varDecl", @@ -1029,22 +1064,27 @@ function buildArrayHofFn( { kind: "if", cond: hasElem, - // A predicate that never reads the element leaves the slot unread: - // a kept element is appended as it is stored (arraySlotAppend), so - // one the element type cannot hold stays in the result. + // A kept element is appended as it was stored, so one the element + // type cannot hold stays in the result. then: copied ? [ + ...arraySlotSnapshot( + lowerer, + varRef("a.0", arrT, loc), + varRef("i.0", F64, loc), + elem, + FILTER_SNAPSHOT, + loc, + ), { kind: "if", - cond: filterCond(callF(getElem), fnRet, loc), + cond: filterCond(callF(argElem), fnRet, loc), then: [ - arraySlotAppend( + arraySnapshotAppend( lowerer, varRef("out.0", outT, loc), - varRef("a.0", arrT, loc), - varRef("i.0", F64, loc), elem, - "v.0", + FILTER_SNAPSHOT, loc, ), ], @@ -1094,7 +1134,7 @@ function buildArrayHofFn( { kind: "if", cond: hasElem, - then: [{ kind: "exprStmt", expr: callF(getElem), loc }], + then: [{ kind: "exprStmt", expr: callF(argElem), loc }], else_: null, loc, }, @@ -1134,13 +1174,17 @@ export function lowerArrayFindLikeCall( presentElements(lowerer, call, elem) && ((method !== "find" && method !== "findLast") || (!bindUntyped && lowerer.coercibleValue(elem, lowerer.irTypeOf(call)))); - const { fnArg, arity, unread } = lowerArrayCallback( + const lowered = lowerArrayCallback( lowerer, argNode, [callbackValueType(lowerer, elem, present)], arrT, bindUntyped, ); + const { fnArg, arity } = lowered; + // Only an array that can hold elements outside its element type needs to + // leave an element its callback ignores unread. + const unread = lowered.unread && !present && elementMayBeForeign(lowerer, elem); // JS takes the ToBoolean of the predicate's result — allowed wherever // that ToBoolean has a static answer (bool passes through; f64/string // by value; a truthy-answerable union by its arm — the @@ -1360,32 +1404,49 @@ function findHelper( ), loc, }; - // A predicate that takes no element, or never reads it, reads it only - // once it holds: the element of a visit it rejects stays unread. + // A predicate that ignores the element of an array that can hold + // elements outside its element type keeps a snapshot of the slot, read + // before the predicate runs as JavaScript reads it (arraySlotSnapshot): + // the predicate is called without reading it as typed, and a found element + // outside the element type takes the checked conversion into the result. + const ignored = (arity === 0 || unread) && !present && elementMayBeForeign(lowerer, elem); const placeholder = unread ? unreadArgument(lowerer, valueT, loc) : null; - const visitBody: IrStmt[] = - arity === 0 || placeholder - ? [ - { - kind: "if", - cond: predicateToBoolean( - callArrayCallback(arrT, fnT, fnRet, arity, placeholder ?? v, loc), - ), - then: [read, { kind: "return", value: found, loc }], - else_: null, - loc, - }, - ] - : [ - read, - { - kind: "if", - cond: predicateToBoolean(callArrayCallback(arrT, fnT, fnRet, arity, v, loc)), - then: [{ kind: "return", value: found, loc }], - else_: null, - loc, - }, - ]; + const visitBody: IrStmt[] = ignored + ? [ + ...arraySlotSnapshot( + lowerer, + varRef("a.0", arrT, loc), + varRef("i.0", F64, loc), + elem, + FILTER_SNAPSHOT, + loc, + ), + { + kind: "if", + cond: predicateToBoolean( + callArrayCallback(arrT, fnT, fnRet, arity, placeholder ?? v, loc), + ), + then: [ + { + kind: "return", + value: arraySnapshotValue(lowerer, elem, FILTER_SNAPSHOT, resultT, () => found, loc), + loc, + }, + ], + else_: null, + loc, + }, + ] + : [ + read, + { + kind: "if", + cond: predicateToBoolean(callArrayCallback(arrT, fnT, fnRet, arity, v, loc)), + then: [{ kind: "return", value: found, loc }], + else_: null, + loc, + }, + ]; const visit: IrStmt[] = visitBody; const loop = last ? reverseArrayLoop(loc, visit) @@ -1402,6 +1463,9 @@ function findHelper( locals: [ ...arrayCallbackLocals(arrT, fnT), { id: "v.0", name: "v", type: valueT, mutable: false }, + ...(ignored + ? slotSnapshotLocals(lowerer, elem, FILTER_SNAPSHOT).filter((l) => l.id !== "v.0") + : []), ], body, loc, @@ -1627,12 +1691,16 @@ export function lowerArrayFlatMapCall( const argNode = call.arguments.nodes[0]; if (!argNode) lowerer.unsupported("SC1090", call, "this call form"); // tsc-guarded const present = presentElements(lowerer, call, elem); - const { fnArg, arity, unread } = lowerArrayCallback( + const lowered = lowerArrayCallback( lowerer, argNode, [callbackValueType(lowerer, elem, present)], arrT, ); + const { fnArg, arity } = lowered; + // Only an array that can hold elements outside its element type needs to + // leave an element its callback ignores unread. + const unread = lowered.unread && !present && elementMayBeForeign(lowerer, elem); const fnRet = fnArg.type.ret; if ( fnRet.kind === "dyn" || @@ -2206,7 +2274,7 @@ export function lowerArrayReduceCall( lowerer.runtimeOptionalReduceTypes.get(call) ?? (hasInit ? lowerer.irTypeOf(call) : arrayValueType(lowerer, elem)); if (accT.kind === "void" || accT.kind === "func") lowerer.badType(call, lowerer.typeOf(call)); - const { fnArg, arity, unread } = lowerArrayCallback( + const lowered = lowerArrayCallback( lowerer, argNode, [accT, arrayValueType(lowerer, elem)], @@ -2214,6 +2282,10 @@ export function lowerArrayReduceCall( false, accT, ); + const { fnArg, arity } = lowered; + // Only an array that can hold elements outside its element type needs to + // leave an element its callback ignores unread. + const unread = lowered.unread && elementMayBeForeign(lowerer, elem); const fast = unread ? null : lowerFastCallback( diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 0e64f51920..5cdbe4d31d 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -6,6 +6,7 @@ import { AmbientReceiverReaders } from "../../../ir/receiver-readers.js"; import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js"; import { BOOL, + DYN, F64, VOID, type IrBindingSource, @@ -32,11 +33,14 @@ import { import { arrayIndexPresent, arrayValueRead, - arraySlotAppend, + arraySlotSnapshot, + arraySnapshotAppend, + arraySnapshotValue, arrayValueStore, arrayValueType, elementMayBeForeign, unreadArgument, + type SlotSnapshot, } from "../array-values.js"; import type { Lowerer } from "../lowerer.js"; @@ -615,7 +619,13 @@ function chainCallbacks(chain: Chain): Stage[] | null { /** The values a fused loop hands from one stage to the next: a local of a * known type, or (before the first read) the source slot itself. */ -type Flow = { kind: "value"; id: string; type: IrType } | { kind: "slot" }; +/** What reaches a stage: a value, the source slot not yet read, or a + * snapshot of the slot read before an earlier callback that ignored it + * (arraySlotSnapshot). */ +type Flow = + | { kind: "value"; id: string; type: IrType } + | { kind: "slot" } + | { kind: "snapshot"; ids: SlotSnapshot }; interface Plan { chain: Chain; @@ -1654,6 +1664,51 @@ class FusedLoop { return []; }; + /** Bring `flow` to the callback of `stage`, whose element parameter is + * at `at`. A slot is read for a callback that reads it. For one that + * ignores it, the slot of an array that can hold elements outside its + * element type is read as stored before the callback runs (a snapshot, + * as JavaScript reads the element first), so such an element passes the + * callback as it is; other arrays read the slot as usual. A snapshot + * reaching a callback that reads it becomes its value, and an element + * outside the element type takes the ordinary read's checked + * conversion there. */ + const reach = ( + stage: Stage, + at: number, + flow: Flow, + rest: (flow: Flow) => IrStmt[], + ): IrStmt[] => { + const reads = stage.info.arity > at && !unreadSlot(stage, at); + if (flow.kind === "slot") { + if (reads || !elementMayBeForeign(this.lowerer, arrT.elem)) + return read(stage, (value) => rest(value)); + const ids: SlotSnapshot = { + isForeign: this.local("sf", BOOL), + value: this.local("sv", arrayValueType(this.lowerer, arrT.elem)), + foreign: this.local("sd", DYN), + }; + return [ + ...arraySlotSnapshot(this.lowerer, a, i, arrT.elem, ids, loc), + ...rest({ kind: "snapshot", ids }), + ]; + } + if (flow.kind === "snapshot" && reads) { + const valueT = arrayValueType(this.lowerer, arrT.elem); + const u = this.local("u", valueT); + return [ + { + kind: "varDecl", + localId: u, + init: arraySnapshotValue(this.lowerer, arrT.elem, flow.ids, valueT, (v) => v, loc), + loc, + }, + ...rest({ kind: "value", id: u, type: valueT }), + ]; + } + return rest(flow); + }; + /** Turn the source slot into a value for the callback of `stage`. */ const read = (stage: Stage, rest: (flow: Flow & { kind: "value" }) => IrStmt[]): IrStmt[] => { if (stage.info.present) { @@ -1709,16 +1764,12 @@ class FusedLoop { }; /** The statements that run one value through stages k.. and the terminal. */ - const visit = (k: number, flow: Flow): IrStmt[] => { + const visit = (k: number, flow: Flow, reached = false): IrStmt[] => { if (k === chain.stages.length) return finish(flow); const stage = chain.stages[k]!; const info = stage.info; - // A callback that declares no element parameter, or never reads the - // one it declares, leaves the slot unread, so an element outside the - // element type passes it as it is (a kept one is copied by the next - // stage or the collecting terminal). - if (flow.kind === "slot" && info.arity > 0 && !unreadSlot(stage, 0)) - return read(stage, (value) => visit(k, value)); + if (flow.kind !== "value" && !reached) + return reach(stage, 0, flow, (next) => visit(k, next, true)); const called = deliver(stage, k, [], flow); if (!called) return fail(); const { pre, value: result } = called; @@ -1815,20 +1866,23 @@ class FusedLoop { } }; - /** Append the source slot to the collected result unread: a filter - * whose callback never read the element keeps it as it is stored - * (arraySlotAppend). */ - const copySlot = (): IrStmt[] => { + /** Append a snapshot of the source slot to the collected result: a + * filter whose callback ignored the element keeps it as it was stored + * (arraySnapshotAppend). */ + const copySlot = (flow: Flow & { kind: "snapshot" }): IrStmt[] => { const outT = resultT as IrType & { kind: "array" }; if (holes || !typeEquals(outT.elem, arrT.elem)) return fail(); - const v = this.local("v", arrayValueType(this.lowerer, arrT.elem)); - return [arraySlotAppend(this.lowerer, varRef(out!, resultT, loc), a, i, arrT.elem, v, loc)]; + return [ + arraySnapshotAppend(this.lowerer, varRef(out!, resultT, loc), arrT.elem, flow.ids, loc), + ]; }; /** The terminal's action for one value. */ - const finish = (flow: Flow): IrStmt[] => { + const finish = (flow: Flow, reached = false): IrStmt[] => { if (terminal.kind === "length") return [increment(count!, loc)]; if (terminal.kind === "collect") { - if (flow.kind !== "value") return copySlot(); + if (flow.kind === "snapshot") return copySlot(flow); + if (flow.kind === "slot") + return read(chain.stages[chain.stages.length - 1]!, (value) => finish(value)); const last = chain.stages[chain.stages.length - 1]!; const outT = resultT as IrType & { kind: "array" }; if (!typeEquals(outT.elem, last.info.outElem)) return fail(); @@ -1850,8 +1904,8 @@ class FusedLoop { const stage = terminal.stage; const method = stage.info.method; const lead = method === "reduce" ? 1 : 0; - if (flow.kind === "slot" && stage.info.arity > lead && !unreadSlot(stage, lead)) - return read(stage, (value) => finish(value)); + if (flow.kind !== "value" && !reached) + return reach(stage, lead, flow, (next) => finish(next, true)); if (method === "reduce") { const accT = stage.info.outElem; const called = deliver(stage, chain.stages.length, [varRef(acc!, accT, loc)], flow); diff --git a/tests/corpus/8014-foreign-element-callbacks.ts b/tests/corpus/8014-foreign-element-callbacks.ts index b971b40516..188a3119de 100644 --- a/tests/corpus/8014-foreign-element-callbacks.ts +++ b/tests/corpus/8014-foreign-element-callbacks.ts @@ -58,3 +58,61 @@ console.log(sparse.filter(() => true), sparse.map((_, i) => i), sparse.findIndex const plain = [1, 2, 3]; console.log(plain.map((v) => ({ v })), plain.reduce((acc, v) => ({ sum: acc.sum + v, v }), { sum: 0, v: 0 })); console.log(plain.filter((v) => [{ v }].length > 0 && v > 1)); + +// A callback that ignores its element still receives the element as it was +// before the callback ran: filter keeps it, and find returns it, even when +// the callback overwrites or removes the slot. +const changing: number[] = [1, 2, 3]; +const changingView: unknown[] = changing; +changingView.push("x"); +let at = 0; +console.log( + changing.filter(() => { + changingView[at] = at === 1 ? "w" : 100 + at; + at++; + return true; + }), + changing, +); +const found: number[] = [1, 2]; +(found as unknown[]).push("y"); +let step = 0; +console.log( + found.find(() => { + found[step] = 50; + step++; + return step === 2; + }), + found, +); +const shrinking: number[] = [4, 5]; +(shrinking as unknown[]).push("z", 6); +console.log( + shrinking.findLast(() => { + shrinking.length = 1; + return true; + }), + shrinking.length, +); +const cut: number[] = [7, 8, 9]; +(cut as unknown[]).push("q"); +console.log( + cut.filter((_v, i) => { + if (i === 1) cut.length = 1; + return true; + }), + cut.length, +); + +// Arrays proven to hold no undefined keep their elements in filter results +// when the predicate ignores them. +const dense = [1, 2, 3, 4]; +let calls = 0; +console.log( + dense.filter((_v, i) => { + calls++; + return i % 2 === 0; + }), + calls, + dense.filter((_v, i) => i > 1), +); From 04c0e5c66ef6998f3f1e90f4493ee0db7e1d0303 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 23:57:36 -0500 Subject: [PATCH 39/49] Cache named child nodes and text on AST nodes The frontend reads the same children of the same nodes many times: on a tsc-ts build, `expression` is read 29.6M times over 253K nodes, `name` 16.4M times over 176K and `text` 26.8M times over 456K. Each read went through AstFile.namedChildAt (the file check, the child mask, the packed child table) and AstFile.node (the node table), and `text` through the wire's kind switch and string table. - AstNode.childNodeAt keeps the child nodes it has read, by wire slot, in a per-node array sized to the node's highest named child. - AstNode.text keeps the text it has read. - AstFile's packed child table is a Uint32Array from the start (empty until the first named child is read), so namedChildAt no longer retains and releases an optional value on every call. Cold tsc-ts build with the native compiler, 16 interleaved rounds: lower 31.68 -> 30.46 s, wall 78.81 -> 76.56 s (faster in every round); peak RSS +105 MB, which the frontend-dispose heap trim keeps below main's peak. --- .../src/frontend/ts7/ast-node.test.ts | 23 +++++++++ .../compiler/src/frontend/ts7/ast-node.ts | 47 +++++++++++++++---- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/ts7/ast-node.test.ts b/packages/compiler/src/frontend/ts7/ast-node.test.ts index 8b391e6221..b650e7725b 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.test.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.test.ts @@ -473,6 +473,29 @@ test("named child access shares node and list identities without materializing s expect(() => file.namedChild(another, "name")).toThrow("another source file"); }); +test("named child getters and text keep answering the first read", () => { + let materialized = 0; + const file = new AstFile(decoded.get("main.ts")!.bytes, () => { + materialized++; + }); + let index = 1; + while (file.wire.kind(index) !== AstKind.FunctionDeclaration) index++; + const node = file.node(index); + const name = node.name; + expect(name?.index).toBe(file.wire.namedChild(index, "name")); + const after = materialized; + expect(node.name).toBe(name); + expect(node.childNode("name")).toBe(name); + expect(file.node(name!.index)).toBe(name); + expect(node.typeParameters).toBeUndefined(); + expect(materialized).toBe(after); + const text = name!.text; + expect(text).toBe(file.wire.text(name!.index)); + expect(name!.text).toBe(text); + expect(node.text).toBeUndefined(); + expect(node.text).toBeUndefined(); +}); + test("warming a named child preserves lazy failures in later malformed sibling links", () => { const bytes = decoded.get("main.ts")!.bytes; const wire = new AstFile(bytes).wire; diff --git a/packages/compiler/src/frontend/ts7/ast-node.ts b/packages/compiler/src/frontend/ts7/ast-node.ts index 4c7726c004..c49dceffb4 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.ts @@ -83,6 +83,9 @@ export interface AstFileOptions { writeTargets?: boolean; } +const NO_CHILD_INDICES = new Uint32Array(0); +const NO_CHILD_NODES: AstNode[] = []; + export class AstFile { readonly wire: AstWireFile; readonly root: AstNode; @@ -94,7 +97,9 @@ export class AstFile { private readonly structuredNodes = new Map(); private readonly strings = new Map(); private lines: number[] | undefined; - private childIndices: Uint32Array | undefined; + /** Resolved named children, eight slots per node; empty until the first + * named child is read. */ + private childIndices: Uint32Array = NO_CHILD_INDICES; /** The root source file's name (AstNode.fileName). */ private rootFileName: string | undefined; @@ -164,8 +169,8 @@ export class AstFile { const mask = node.data >>> 30 === 0 ? node.data & 0xff : 0xff; if ((mask & (1 << order)) === 0) return 0; const slot = node.index * 8 + order; - const indices = this.childIndices; - if (indices !== undefined) { + let indices = this.childIndices; + if (indices.length !== 0) { const cached = indices[slot]!; if (cached !== 0) return cached; } @@ -173,9 +178,11 @@ export class AstFile { // The pinned wire has at most eight named children per node. One packed // table avoids per-node arrays; allocate it only after successful access // so malformed siblings still fail lazily in the checked wire decoder. - if (this.childIndices === undefined) - this.childIndices = new Uint32Array(this.wire.nodeCount * 8); - this.childIndices[slot] = index; + if (indices.length === 0) { + indices = new Uint32Array(this.wire.nodeCount * 8); + this.childIndices = indices; + } + indices[slot] = index; return index; } @@ -320,6 +327,13 @@ export class AstNode { readonly data: number; private parentResolved = false; private parentCache: AstNode | undefined; + /** The node's text, read once: the frontend asks the same identifiers + * for their text many times. */ + private textResolved = false; + private textCache: string | undefined; + /** Named child nodes by wire slot, filled as they are read; empty until + * the first one is. Slots the child mask leaves empty are never stored. */ + private childNodes: AstNode[] = NO_CHILD_NODES; constructor( readonly file: AstFile, @@ -351,7 +365,11 @@ export class AstNode { return this.parentCache; } get text(): string | undefined { - return this.file.wire.text(this.index); + if (!this.textResolved) { + this.textCache = this.file.wire.text(this.index); + this.textResolved = true; + } + return this.textCache; } get rawText(): string | undefined { return this.file.wire.rawText(this.index); @@ -363,8 +381,21 @@ export class AstNode { } /** childNode by wire slot, as the generated getters select it. */ childNodeAt(order: number): AstNode | undefined { + let nodes = this.childNodes; + if (order >= 0 && order < nodes.length) { + const cached = nodes[order]; + if (cached !== undefined) return cached; + } const index = this.file.namedChildAt(this, order); - return index === 0 ? undefined : this.file.node(index); + if (index === 0) return undefined; + const child = this.file.node(index); + if (nodes.length === 0) { + const mask = this.data >>> 30 === 0 ? this.data & 0xff : 0xff; + nodes = new Array(32 - Math.clz32(mask)); + this.childNodes = nodes; + } + nodes[order] = child; + return child; } childListAt(order: number): AstNodeList | undefined { const index = this.file.namedChildAt(this, order); From feb4501013f626f373ad2d18f7c272a3d5ec1df2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 00:24:05 -0500 Subject: [PATCH 40/49] Keep a candidate's generation when a limited cycle pass reaches it A limited pass (scr_cyc_collect_set, which freeing a LifetimeRegion runs over the objects the region owned) takes only the candidates in its set. Its walk can still reach other candidates, and one that is referenced from outside is blackened by scan and promoted to the next generation while it keeps its slot in its old generation's buffer. scr_cyc_on_dead finds a candidate's buffer by its generation, so when that object died it removed another buffer's entry and left its own slot pointing at the freed block, which the next pass then read. Such a candidate is no longer promoted: it keeps the generation of the buffer it is in until a pass takes it. --- ...5-lifetime-region-shared-candidate.ts.json | 6 ++ packages/runtime/src/scr_cycle.c | 5 +- packages/runtime/test/test_cycle.c | 39 +++++++++ .../5025-lifetime-region-shared-candidate.ts | 85 +++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5025-lifetime-region-shared-candidate.ts.json create mode 100644 tests/corpus/5025-lifetime-region-shared-candidate.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5025-lifetime-region-shared-candidate.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5025-lifetime-region-shared-candidate.ts.json new file mode 100644 index 0000000000..73b06cc549 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5025-lifetime-region-shared-candidate.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5025-lifetime-region-shared-candidate.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index 2b942e0111..d421e8e45a 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -1474,7 +1474,10 @@ static void scr_scan_black(void *obj, uintptr_t depth) { const ScrCycType *type = scr_cyc_type(h); bool acyclic = type->trace == scr_cyc_no_children; if (!scr_cyc_is_buffered(h)) h->buf = acyclic ? SCR_CYC_ACYCLIC : 0; - if (scr_cyc_gen(h) < SCR_CYC_OLD) scr_cyc_push_work(&scr_promote, obj); + /* Such a candidate also keeps its generation: the generation names the + * buffer its slot is in (scr_cyc_on_dead), so it is promoted only once a + * pass takes it. */ + if (scr_cyc_gen(h) < SCR_CYC_OLD && !scr_cyc_is_buffered(h)) scr_cyc_push_work(&scr_promote, obj); if (acyclic) return; if (depth == SCR_CYC_WALK_DEPTH) { scr_cyc_push_work(&scr_restore_pending, obj); diff --git a/packages/runtime/test/test_cycle.c b/packages/runtime/test/test_cycle.c index dc48abd263..08eaecad41 100644 --- a/packages/runtime/test/test_cycle.c +++ b/packages/runtime/test/test_cycle.c @@ -1314,6 +1314,44 @@ static void check_limited_pass(void) { check(freed - before == 4 * RING, "the live ring was not collected once dead"); } +/* A limited pass that reaches a live candidate it did not take leaves the + * candidate in its slot and in its generation, which names the slot's + * buffer: when the candidate dies afterwards, it leaves that buffer, and no + * later pass meets its freed block. */ +static void check_limited_pass_keeps_untaken_generation(void) { + enum { RING = 16 }; + if (configured_nursery_threshold() < 8) return; + scr_collect_cycles(); + size_t before = freed; + static ScrCycType cyc_type = { &node_trace, &node_free, 0 }; + Node *shared = scr_cyc_alloc(sizeof(*shared), &cyc_type); + shared->rc = 1; /* its owner */ + /* A dead ring the filter takes points at it. */ + Node *dead = make_ring(RING); + dead->other = shared; + shared->rc++; + /* It loses a reference and becomes a candidate the filter refuses. */ + shared->rc++; + release_live(shared); + release_live(dead); + ScrCycHdr *h = scr_cyc_hdr(shared); + unsigned gen = scr_cyc_gen(h); + check(scr_cyc_is_buffered(h), "the shared node is not a candidate"); + take(dead); + check(scr_cyc_collect_set(&taken, SCR_CYC_NURSERY) == RING, + "limited pass did not free the taken ring"); + check(scr_cyc_is_buffered(h) && scr_cyc_gen(h) == gen, + "limited pass moved a candidate it did not take out of its generation"); + memset(taken_slots, 0, sizeof taken_slots); + /* Its owner lets go: it dies directly, out of its buffer. */ + check(shared->rc == 1, "the shared node kept a reference from the freed ring"); + shared->rc--; + scr_cyc_on_dead(shared); + node_free(shared); + scr_collect_cycles(); + check(freed - before == RING + 1, "the passes after the limited one freed something else"); +} + /* A full pass whose fullest candidate buffer is the old generation's (more * entries than a pass keeps between passes), with nursery candidates * beside it: every dead ring in either buffer is reclaimed, the live rings @@ -1593,6 +1631,7 @@ int main(void) { check_allocation_growth_runs_passes(); check_publication_takes_candidates(); check_limited_pass(); + check_limited_pass_keeps_untaken_generation(); check_large_old_buffer_with_nursery_candidates(); check_unjudged_backlog_follows_growth(); check_acyclic_objects(); diff --git a/tests/corpus/5025-lifetime-region-shared-candidate.ts b/tests/corpus/5025-lifetime-region-shared-candidate.ts new file mode 100644 index 0000000000..7b5084a056 --- /dev/null +++ b/tests/corpus/5025-lifetime-region-shared-candidate.ts @@ -0,0 +1,85 @@ +// LifetimeRegion (@scriptc/experimental): freeing a region whose objects +// own a cycle runs a limited cycle pass over what the region owned. That +// pass also reaches a shared object the region pointed at, which is a +// candidate of its own (it lost a reference) and stays referenced from +// outside. The shared object must stay a valid candidate after the pass: +// it dies afterwards, later passes run, and the program prints the same +// as under Node.js. +import type { ProgramLifetime } from "@scriptc/types"; +import { LifetimeRegion } from "@scriptc/experimental"; + +class Shared { + readonly label: string; + peers: Shared[] = []; + constructor(label: string) { + this.label = label; + } +} + +// Ordinary objects the region's instance owns: a cycle that points back +// into the region and at the shared object. +class Owned { + holder: Holder | undefined = undefined; + shared: Shared | undefined = undefined; + partner: Owned | undefined = undefined; +} + +class Holder implements ProgramLifetime { + owned: Owned | undefined = undefined; + readonly id: number; + constructor(id: number) { + this.id = id; + } +} + +// Cyclic garbage, so the collector runs passes of its own. +class Ring { + next: Ring | undefined = undefined; + readonly value: number; + constructor(value: number) { + this.value = value; + } +} + +function churn(n: number): number { + let total = 0; + for (let i = 0; i < n; i++) { + const a = new Ring(i); + const b = new Ring(i + 1); + a.next = b; + b.next = a; + total += a.value + b.next.value; + } + return total; +} + +function round(k: number): string { + let shared: Shared | undefined = new Shared(`shared ${k}`); + // A second reference, dropped below: the shared object becomes a candidate. + let keep: Shared[] = [shared]; + shared.peers.push(new Shared(`peer ${k}`)); + const region = new LifetimeRegion(); + region.run(() => { + const holder = new Holder(k); + const owned = new Owned(); + const partner = new Owned(); + owned.partner = partner; + partner.partner = owned; + owned.holder = holder; + owned.shared = shared; + holder.owned = owned; + }); + keep = []; + region.release(); + const label = shared.label + " / " + shared.peers[0]!.label + " / " + keep.length; + // The shared object dies, and the passes that follow read the candidate buffers. + shared = undefined; + churn(2000); + return label; +} + +for (let k = 0; k < 20; k++) { + const label = round(k); + if (k % 5 === 0) console.log(label); +} +console.log("churn", churn(10000)); From 2e142d6121f1631d4afe6e66a90559bde75cdba9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 00:40:44 -0500 Subject: [PATCH 41/49] Find bridgePlainUnwindCalls' calls and mentions with indexOf bridgePlainUnwindCalls rewrites the module text after emission on Linux. It split the text with a lookahead regular expression, ran a regular expression over every line to find mentions of raising functions, and split every function that has calls into lines to test each one. - The text splits before each `define` line with indexOf. - Mentions of raising functions are found by scanning for `@` and reading the name that follows it. - A function without a plain call of a raising function rewrites only the lines that hold `call `, marking them nounwind. A function with such a call takes the line-by-line path as before. The emitted text is unchanged. --- .../src/backend/llvm/unwind-abi.test.ts | 40 +++++++ .../compiler/src/backend/llvm/unwind-abi.ts | 101 +++++++++++++++--- 2 files changed, 129 insertions(+), 12 deletions(-) diff --git a/packages/compiler/src/backend/llvm/unwind-abi.test.ts b/packages/compiler/src/backend/llvm/unwind-abi.test.ts index 5f7f5895bd..bcef1c3796 100644 --- a/packages/compiler/src/backend/llvm/unwind-abi.test.ts +++ b/packages/compiler/src/backend/llvm/unwind-abi.test.ts @@ -283,3 +283,43 @@ test("calls that cannot unwind are marked nounwind", () => { expect(out).toContain("%r = call ptr %b(ptr %b) nounwind"); expect(out).toContain(" call void @scr_unwind_raise()\n"); }); + +test("functions without a plain call of a raising function change only their call lines", () => { + const raising = [ + "define internal void @sc_bf_raises() #0 {", + "entry:", + " call void @scr_unwind_raise()", + " unreachable", + "}", + ].join("\n"); + const other = [ + "define internal void @sc_bf_raises.copy() #0 {", + "entry:", + " ret void", + "}", + ].join("\n"); + const plain = [ + "define internal double @sc_f_plain(ptr %p) #0 {", + "entry: ; call sites below", + " %a = call double @sc_f_other(ptr %p)", + " %b = tail call double @sc_f_other(ptr %p) ; mentions @ and @sc_bf_raises.copy", + " store ptr @sc_bf_raises.copy, ptr %p", + " ret double %a", + "}", + ].join("\n"); + const parts = [`${raising}\n${other}`, plain, "@table = internal constant ptr @sc_f_plain"]; + const out = bridgePlainUnwindCalls(parts); + expect(out[0]).toBe(parts[0]); + expect(out[1]).toBe( + plain + .replace("@sc_f_other(ptr %p)\n", "@sc_f_other(ptr %p) nounwind\n") + .replace("@sc_f_other(ptr %p) ;", "@sc_f_other(ptr %p) nounwind ;"), + ); + expect(out[2]).toBe(parts[2]); + expect(() => + bridgePlainUnwindCalls([ + raising, + plain.replace("store ptr @sc_bf_raises.copy", "store ptr @sc_bf_raises"), + ]), + ).toThrow(UnwindAbiViolation); +}); diff --git a/packages/compiler/src/backend/llvm/unwind-abi.ts b/packages/compiler/src/backend/llvm/unwind-abi.ts index 4dc6119132..91722fb1dd 100644 --- a/packages/compiler/src/backend/llvm/unwind-abi.ts +++ b/packages/compiler/src/backend/llvm/unwind-abi.ts @@ -301,6 +301,46 @@ function markNounwind(line: string): string { : `${line.slice(0, close + 1)} nounwind${line.slice(close + 1)}`; } +/** The functions of `part`: its text split before each `define` line + * (`part.split(/\n(?=define )/)`), found with indexOf. */ +function splitFunctions(part: string): string[] { + const chunks: string[] = []; + let start = 0; + for (let at = part.indexOf("\ndefine "); at >= 0; at = part.indexOf("\ndefine ", at + 1)) { + chunks.push(part.slice(start, at)); + start = at + 1; + } + chunks.push(part.slice(start)); + return chunks; +} + +/** Whether `code` can be part of a symbol name (`[\w.$%-]`). */ +function isNameCode(code: number): boolean { + return ( + (code >= 97 && code <= 122) || + (code >= 65 && code <= 90) || + (code >= 48 && code <= 57) || + code === 95 /* _ */ || + code === 46 /* . */ || + code === 36 /* $ */ || + code === 37 /* % */ || + code === 45 /* - */ + ); +} + +/** Calls `each` with every line of `chunk` that holds `call `, by its start + * and end offsets, once per line and in order. Lines without `call ` match + * neither CALL_LINE nor PLAIN_CALL. */ +function eachCallLine(chunk: string, each: (start: number, end: number) => void): void { + for (let at = chunk.indexOf("call "); at >= 0;) { + const start = chunk.lastIndexOf("\n", at) + 1; + let end = chunk.indexOf("\n", at); + if (end < 0) end = chunk.length; + each(start, end); + at = chunk.indexOf("call ", end); + } +} + /** The value a bridged call yields on its landing pad: the dummy a * pending-flag callee returns with an exception pending. */ function dummyValue(type: string): string { @@ -318,7 +358,7 @@ function dummyValue(type: string): string { * these functions that emitDirectCall lowered are invokes already. */ export function bridgePlainUnwindCalls(parts: string[]): string[] { const raisers = new Set(); - const split = parts.map((part) => part.split(/\n(?=define )/)); + const split = parts.map(splitFunctions); for (const chunks of split) for (const chunk of chunks) { const m = DEFINE_NAME.exec(chunk); @@ -359,20 +399,29 @@ export function bridgePlainUnwindCalls(parts: string[]): string[] { } } for (const name of invoked) if (!raisers.has(name) && testsFlag.has(name)) violations.add(name); + // Every mention of a raising function outside its `define` line, other + // than as the callee of a call or invoke, found with indexOf. for (const chunks of split) for (const chunk of chunks) - for (const line of chunk.split("\n")) { - if (line.startsWith("define ")) continue; - for (const m of line.matchAll(/@([\w.$%-]+)/g)) { - if (!raisers.has(m[1]!)) continue; - const at = m.index!; - if ( - /(?:call|invoke) [^@]*$/.test(line.slice(0, at)) && - line.charAt(at + m[0].length) === "(" - ) - continue; - violations.add(m[1]!); + for (let at = chunk.indexOf("@"); at >= 0;) { + let end = at + 1; + while (end < chunk.length && isNameCode(chunk.charCodeAt(end))) end++; + const next = chunk.indexOf("@", end); + if (end === at + 1) { + at = next; + continue; } + const name = chunk.slice(at + 1, end); + if (raisers.has(name)) { + const start = chunk.lastIndexOf("\n", at) + 1; + const line = chunk.slice(start, at); + if ( + !line.startsWith("define ") && + !(/(?:call|invoke) [^@]*$/.test(line) && chunk.charAt(end) === "(") + ) + violations.add(name); + } + at = next; } if (violations.size > 0) throw new UnwindAbiViolation(violations); let personality = false; @@ -381,6 +430,34 @@ export function bridgePlainUnwindCalls(parts: string[]): string[] { chunks .map((chunk) => { if (!DEFINE_NAME.test(chunk) || !chunk.includes("call ")) return chunk; + // A function without a plain call of a raising function only marks + // its calls nounwind: rewrite just its call lines. + let bridged = false; + const marks: { start: number; end: number; line: string }[] = []; + eachCallLine(chunk, (start, end) => { + if (bridged) return; + const line = chunk.slice(start, end); + const m = PLAIN_CALL.exec(line); + const callee = m === null ? undefined : (m[4] ?? m[7]); + if (m !== null && callee !== undefined && raisers.has(callee)) { + bridged = true; + return; + } + if (!CALL_LINE.test(line)) return; + const marked = markNounwind(line); + if (marked !== line) marks.push({ start, end, line: marked }); + }); + if (!bridged) { + if (marks.length === 0) return chunk; + const pieces: string[] = []; + let from = 0; + for (const mark of marks) { + pieces.push(chunk.slice(from, mark.start), mark.line); + from = mark.end; + } + pieces.push(chunk.slice(from)); + return pieces.join(""); + } const lines = chunk.split("\n"); const resume = resumers.has(DEFINE_NAME.exec(chunk)![1]!); const result: string[] = []; From 33be2a80ee4464c7730d374029ce04dbd87031d3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 01:04:42 -0500 Subject: [PATCH 42/49] Store a table entry for if/else chains that store literals to one element An if/else-if chain that compares one number local with integer literals and stores a number literal to the same typed-array element in every arm, including a final else, such as if (x === 97) a[i] = 0; else if (x === 116) a[i] = 1; else if (x === 99) a[i] = 2; else a[i] = 3; is now emitted as one store of an entry of a constant table indexed by the local's value (constant-choices.ts). On input whose cases do not repeat in a pattern, such as the bases of a DNA sequence, the branches mispredict; LLVM keeps them, because cases this far apart are too sparse for its own switch tables. The chain needs at least three distinct values within 256 of each other. The table covers the cases' span, or the local's proven integer range when that range has at most 256 values, in which case no test is emitted. Otherwise a value that is not an exact integer in the table's span (NaN, a fraction, or a value outside it) stores the final else's literal, chosen with a select. The first arm wins for a repeated value, and -0 selects the arm for 0, as strict equality does. The store is the arms' own (the same receiver and index), converted to the element's type as each arm's would be. Debug builds keep the arms. k-nucleotide's transform of the input sequence uses this form: its median wall time drops from 2.71 s to 2.21 s. --- .../src/backend/llvm/constant-choices.test.ts | 109 ++++++++ .../src/backend/llvm/constant-choices.ts | 236 ++++++++++++++++++ .../src/backend/llvm/emit-statements.ts | 8 + .../corpus/constant-choice-chains.ts.json | 6 + tests/corpus/constant-choice-chains.ts | 99 ++++++++ .../constant-choice-chains.ts.json | 1 + 6 files changed, 459 insertions(+) create mode 100644 packages/compiler/src/backend/llvm/constant-choices.test.ts create mode 100644 packages/compiler/src/backend/llvm/constant-choices.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/constant-choice-chains.ts.json create mode 100644 tests/corpus/constant-choice-chains.ts create mode 100644 tests/harness/corpus-costs/constant-choice-chains.ts.json diff --git a/packages/compiler/src/backend/llvm/constant-choices.test.ts b/packages/compiler/src/backend/llvm/constant-choices.test.ts new file mode 100644 index 0000000000..f19b5b19d8 --- /dev/null +++ b/packages/compiler/src/backend/llvm/constant-choices.test.ts @@ -0,0 +1,109 @@ +import { expect, test } from "vitest"; +import { BOOL, F64, type IrExpr, type IrStmt } from "../../ir/ir.js"; +import { matchConstantChoice } from "./constant-choices.js"; + +const loc = { file: "choices.ts", start: 0, end: 0 }; +const bytes = { kind: "bytes", elem: "u8" } as const; +const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string, type: IrExpr["type"] = F64): IrExpr => ({ + kind: "varRef", + localId, + type, + loc, +}); +const is = (left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op: "===", + left, + right, + type: BOOL, + loc, +}); +const store = (value: IrExpr, index: IrExpr = ref("i"), arr = "a"): IrStmt => ({ + kind: "bytesSet", + arr: ref(arr, bytes), + index, + value, + loc, +}); +const branch = (cond: IrExpr, then: IrStmt[], else_: IrStmt[] | null): IrStmt => ({ + kind: "if", + cond, + then, + else_, + loc, +}); +/** `if (x === c0) a[i] = r0; else if ... else a[i] = fallback`. */ +function chain(cases: [number, number][], fallback: IrStmt[] | null, subject = "x"): IrStmt { + let rest = fallback; + for (let k = cases.length - 1; k >= 0; k--) { + const [value, result] = cases[k]!; + rest = [branch(is(ref(subject), num(value)), [store(num(result))], rest)]; + } + return rest![0]!; +} + +test("a chain of literal stores to one element is a constant choice", () => { + const choice = matchConstantChoice( + chain( + [ + [97, 0], + [116, 1], + [99, 2], + ], + [store(num(3))], + ), + ); + expect(choice?.cases).toEqual([ + { value: 97, result: 0 }, + { value: 116, result: 1 }, + { value: 99, result: 2 }, + ]); + expect(choice?.fallback).toBe(3); + expect(choice?.stores).toHaveLength(4); +}); + +test("a literal on the left, -0 and a repeated value keep strict equality's first arm", () => { + const first: IrStmt = branch( + is(num(-0), ref("x")), + [store(num(5))], + [ + chain( + [ + [1, 6], + [0, 7], + [2, 8], + ], + [store(num(9))], + ), + ], + ); + expect(matchConstantChoice(first)?.cases).toEqual([ + { value: 0, result: 5 }, + { value: 1, result: 6 }, + { value: 2, result: 8 }, + ]); +}); + +test("chains that are not one choice of literals keep their arms", () => { + const three: [number, number][] = [ + [1, 10], + [2, 20], + [3, 30], + ]; + // Two values, no final else, a fraction, or cases spanning more than 256 values. + expect(matchConstantChoice(chain(three.slice(0, 2), [store(num(0))]))).toBeNull(); + expect(matchConstantChoice(chain(three, null))).toBeNull(); + expect(matchConstantChoice(chain([...three, [1.5, 40]], [store(num(0))]))).toBeNull(); + expect(matchConstantChoice(chain([...three, [256, 40]], [store(num(0))]))).not.toBeNull(); + expect(matchConstantChoice(chain([...three, [257, 40]], [store(num(0))]))).toBeNull(); + // A final else that is not a literal, or that stores elsewhere. + expect(matchConstantChoice(chain(three, [store(ref("y"))]))).toBeNull(); + expect(matchConstantChoice(chain(three, [store(num(0), ref("j"))]))).toBeNull(); + expect(matchConstantChoice(chain(three, [store(num(0), ref("i"), "b")]))).toBeNull(); + // An arm with a second statement. + expect(matchConstantChoice(chain(three, [store(num(0)), store(num(1))]))).toBeNull(); + // A second subject. + const mixed = branch(is(ref("y"), num(0)), [store(num(1))], [chain(three, [store(num(0))])]); + expect(matchConstantChoice(mixed)).toBeNull(); +}); diff --git a/packages/compiler/src/backend/llvm/constant-choices.ts b/packages/compiler/src/backend/llvm/constant-choices.ts new file mode 100644 index 0000000000..208fac36aa --- /dev/null +++ b/packages/compiler/src/backend/llvm/constant-choices.ts @@ -0,0 +1,236 @@ +import { createHash } from "node:crypto"; +import { InternalCompilerError } from "../../errors.js"; +import type { IrExpr, IrStmt } from "../../ir/ir.js"; +import { exactInteger, widenInteger } from "./integer-values.js"; +import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; +import { f64Lit } from "./common.js"; + +type BytesSet = Extract; + +/** An if/else-if chain that stores one of several numeric literals to the + * same typed-array element, choosing it by comparing one number local with + * integer literals: + * + * if (x === 97) a[i] = 0; else if (x === 116) a[i] = 1; ... else a[i] = 3; + * + * The chain is emitted as one store of a value read from a constant table, + * with no branch on the comparisons. Branches on unpredictable input (the + * bases of a DNA sequence, the bytes of a text) mispredict about half the + * time; LLVM keeps them, because sparse cases (97, 99, 116) are too far + * apart for its own switch tables. */ +export interface ConstantChoice { + subject: IrExpr; + /** The first arm for each distinct value, in chain order. */ + cases: readonly { value: number; result: number }[]; + fallback: number; + /** Every arm's store, the final else's last. */ + stores: readonly BytesSet[]; +} + +/** At least this many distinct values: shorter chains predict as well as a + * table load. */ +const MIN_CASES = 3; +/** The table covers the subject's values from the smallest case (or the + * subject's proven minimum) up to this many entries. */ +const MAX_TABLE = 256; + +function integerLiteral(expr: IrExpr): number | null { + if (expr.kind !== "numLit" || !Number.isSafeInteger(expr.value)) return null; + // `x === -0` and `x === 0` select the same values. + return expr.value === 0 ? 0 : expr.value; +} + +function sameVarRef(a: IrExpr, b: IrExpr): boolean { + return a.kind === "varRef" && b.kind === "varRef" && a.localId === b.localId; +} + +/** A local read or a number literal: reading it has no effect. */ +function sameOperand(a: IrExpr, b: IrExpr): boolean { + if (a.kind === "numLit" && b.kind === "numLit") return Object.is(a.value, b.value); + return sameVarRef(a, b); +} + +/** The store of a single-statement arm, when it stores a number literal to + * an element named by a local and a local or a literal index (so evaluating + * them once, after the comparisons, reads what every arm would). */ +function literalStore(body: readonly IrStmt[] | null): BytesSet | null { + if (body === null || body.length !== 1) return null; + const s = body[0]!; + if (s.kind !== "bytesSet" || s.value.kind !== "numLit") return null; + if (s.arr.kind !== "varRef" || (s.index.kind !== "varRef" && s.index.kind !== "numLit")) + return null; + if (s.arr.type.kind !== "bytes" || s.index.type.kind !== "f64") return null; + return s; +} + +export function matchConstantChoice(s: IrStmt): ConstantChoice | null { + if (s.kind !== "if") return null; + let subject: IrExpr | null = null; + const cases: { value: number; result: number }[] = []; + const seen = new Set(); + const stores: BytesSet[] = []; + let current: IrStmt = s; + for (;;) { + if (current.kind !== "if") return null; + const cond = current.cond; + if (cond.kind !== "bin" || cond.op !== "===") return null; + const [local, literal] = + cond.left.kind === "varRef" ? [cond.left, cond.right] : [cond.right, cond.left]; + if (local.kind !== "varRef" || local.type.kind !== "f64") return null; + if (subject === null) subject = local; + else if (!sameVarRef(subject, local)) return null; + const value = integerLiteral(literal); + if (value === null) return null; + const store = literalStore(current.then); + if (store === null) return null; + stores.push(store); + // A repeated value never reaches its later arm. + if (!seen.has(value)) { + seen.add(value); + cases.push({ value, result: (store.value as IrExpr & { kind: "numLit" }).value }); + } + const rest: readonly IrStmt[] | null = current.else_; + if (rest === null || rest.length !== 1) return null; + if (rest[0]!.kind === "if") { + current = rest[0]!; + continue; + } + const last = literalStore(rest); + if (last === null) return null; + stores.push(last); + break; + } + const first = stores[0]!; + for (const store of stores) { + if (!sameVarRef(store.arr, first.arr) || !sameOperand(store.index, first.index)) return null; + if (store.arr.type.kind !== "bytes" || first.arr.type.kind !== "bytes") return null; + if (store.arr.type.elem !== first.arr.type.elem) return null; + } + if (cases.length < MIN_CASES) return null; + const values = cases.map((c) => c.value); + if (Math.max(...values) - Math.min(...values) >= MAX_TABLE) return null; + const fallback = (stores[stores.length - 1]!.value as IrExpr & { kind: "numLit" }).value; + return { subject: subject!, cases, fallback, stores }; +} + +function int32(value: number): boolean { + return ( + Number.isInteger(value) && value >= -2147483648 && value <= 2147483647 && !Object.is(value, -0) + ); +} + +/** The chosen number: the table entry for the subject's value, or the final + * else's literal for a value no case names (including NaN, a fraction and + * every value outside the table). */ +function emitChoice(host: LlvmEmitterContext, choice: ConstantChoice): LlValue { + const B = host.B; + const subject = host.emitExpr(choice.subject); + const results = [...choice.cases.map((c) => c.result), choice.fallback]; + const integral = results.every(int32); + const element = integral ? "i32" : "double"; + const literal = (value: number): string => (integral ? String(value) : f64Lit(value)); + + const proven = exactInteger(host, subject, choice.subject); + const values = choice.cases.map((c) => c.value); + let low = Math.min(...values); + let high = Math.max(...values); + // A proven range that fits a table needs no test: every value it allows + // has an entry. + const covered = + proven !== null && proven.range.max - proven.range.min < MAX_TABLE ? proven : null; + if (covered !== null) { + low = covered.range.min; + high = covered.range.max; + } + const size = high - low + 1; + const entries: string[] = []; + for (let value = low; value <= high; value++) { + const hit = choice.cases.find((c) => c.value === value); + entries.push(`${element} ${literal(hit === undefined ? choice.fallback : hit.result)}`); + } + const table = `@sc_choice_${createHash("sha256") + .update(`${element}:${entries.join(",")}`) + .digest("hex") + .slice(0, 16)}`; + host.declare( + `${table} = private unnamed_addr constant [${size} x ${element}] [${entries.join(", ")}]`, + ); + + let index: string; + let ok: string | null = null; + if (covered !== null) { + const wide = widenInteger(host, covered); + index = B.tmp(); + B.line(`${index} = sub i64 ${wide}, ${low}`); + } else { + // freeze keeps NaN and out-of-range conversions from producing poison; + // the round trip then rejects every value that is not an exact integer + // (-0 converts to 0, which `=== 0` selects too). + const raw = B.tmp(); + const selector = B.tmp(); + const restored = B.tmp(); + const exact = B.tmp(); + const offset = B.tmp(); + const inside = B.tmp(); + ok = B.tmp(); + index = B.tmp(); + B.line(`${raw} = fptosi double ${subject.name} to i64`); + B.line(`${selector} = freeze i64 ${raw}`); + B.line(`${restored} = sitofp i64 ${selector} to double`); + B.line(`${exact} = fcmp oeq double ${subject.name}, ${restored}`); + B.line(`${offset} = sub i64 ${selector}, ${low}`); + B.line(`${inside} = icmp ult i64 ${offset}, ${size}`); + B.line(`${ok} = and i1 ${exact}, ${inside}`); + B.line(`${index} = select i1 ${ok}, i64 ${offset}, i64 0`); + } + const slot = B.tmp(); + const entry = B.tmp(); + B.line( + `${slot} = getelementptr inbounds [${size} x ${element}], ptr ${table}, i64 0, i64 ${index}`, + ); + B.line(`${entry} = load ${element}, ptr ${slot}`); + let chosen = entry; + if (ok !== null) { + chosen = B.tmp(); + B.line( + `${chosen} = select i1 ${ok}, ${element} ${entry}, ${element} ${literal(choice.fallback)}`, + ); + } + if (!integral) return { name: chosen, type: choice.stores[0]!.value.type }; + const number = B.tmp(); + B.line(`${number} = sitofp i32 ${chosen} to double`); + return { + name: number, + type: choice.stores[0]!.value.type, + uint32: chosen, + integer: { + name: chosen, + type: "i32", + signed: Math.min(...results) < 0, + range: { min: Math.min(...results), max: Math.max(...results) }, + }, + }; +} + +/** Emits a matched chain as one store of the chosen literal. The element + * write is the arms' own: every arm names the same receiver and index. */ +export function emitConstantChoice(host: LlvmEmitterContext, choice: ConstantChoice): void { + const first = choice.stores[0]!; + if (first.arr.type.kind !== "bytes") + throw new InternalCompilerError("llvm emitter bug: constant choice on non-bytes"); + const arr = host.emitStableReceiver(first.arr, [first.index]); + const index = host.emitExpr(first.index); + const value = emitChoice(host, choice); + const scopes = choice.stores.map((store) => host.bytesAliasScopes.get(store)); + host.emitBytesSet( + first.arr.type.elem, + arr.name, + index, + value, + first.index, + undefined, + choice.stores.every((store) => host.bytesBounds.has(store)), + choice.stores.every((store) => host.bytesOwned.has(store)), + scopes.every((scope) => scope === scopes[0]) ? (scopes[0] ?? "") : "", + ); +} diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 66bbf34d0b..8bba1e7cff 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -93,6 +93,7 @@ import { import { emitStringInputs } from "./string-lifetimes.js"; import { LOOPS } from "./loop-state.js"; import { loopAccessesTypedArrays } from "../loop-polls.js"; +import { emitConstantChoice, matchConstantChoice } from "./constant-choices.js"; /** A callback loop version copies the loop; keep the copies small. */ const MAX_CALLBACK_VERSION_COST = 400; @@ -837,6 +838,13 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { break; } case "if": { + // A chain storing one of several literals to one element stores a + // table entry instead (debug builds keep the arms to step through). + const choice = emitter.debug === null ? matchConstantChoice(s) : null; + if (choice !== null) { + emitConstantChoice(emitter, choice); + break; + } const cond = emitter.emitCondition(s.cond); const trueLabel = B.newLabel("if.t"); const joinLabel = B.newLabel("if.j"); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/constant-choice-chains.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/constant-choice-chains.ts.json new file mode 100644 index 0000000000..059a21156b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/constant-choice-chains.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/constant-choice-chains.ts" + ], + "diags": [] +} diff --git a/tests/corpus/constant-choice-chains.ts b/tests/corpus/constant-choice-chains.ts new file mode 100644 index 0000000000..8d749daee4 --- /dev/null +++ b/tests/corpus/constant-choice-chains.ts @@ -0,0 +1,99 @@ +// if/else-if chains that store one of several literals to one typed-array +// element, chosen by comparing one number with integer literals: the +// subject's values that no case names, repeated and negative cases, +// results that do not fit the element, and chains that keep their arms. + +function bases(arr: Uint8Array): void { + for (let i = 0; i < arr.length; ++i) { + const x = arr[i]; + if (x === 97) arr[i] = 0; + else if (x === 116) arr[i] = 1; + else if (x === 99) arr[i] = 2; + else arr[i] = 3; + } +} + +const sequence = new Uint8Array(64); +for (let i = 0; i < sequence.length; i++) sequence[i] = "actgACTGnx"[i % 10]!.charCodeAt(0); +bases(sequence); +console.log("bases", sequence.join("")); + +// A number subject: NaN, -0, fractions, infinities and values far outside the cases. +const subjects = [0, -0, 1, 2, 3, 4, -1, 0.5, 2.0000001, NaN, Infinity, -Infinity, 2 ** 40, -(2 ** 40), 1e300]; +function classify(out: Int16Array, at: number, v: number): void { + if (v === 0) out[at] = 10; + else if (v === 1) out[at] = -20; + else if (v === 2) out[at] = 30; + else if (v === 1) out[at] = 99; // never reached + else if (v === -1) out[at] = 40; + else out[at] = -50; +} +const classes = new Int16Array(subjects.length); +for (let i = 0; i < subjects.length; i++) classify(classes, i, subjects[i]!); +console.log("classes", classes.join(" ")); + +// The literal on the left, and results the element converts: 300 wraps in a +// Uint8Array, -1 is 255, 1.5 truncates, and a clamped array rounds and clamps. +function convert(u8: Uint8Array, c: Uint8ClampedArray, f: Float64Array, i: Int8Array, v: number): void { + if (5 === v) u8[0] = 300; + else if (6 === v) u8[0] = -1; + else if (7 === v) u8[0] = 1.5; + else u8[0] = 8589934599; // 2 ** 33 + 7 + if (v === 5) c[0] = 300; + else if (v === 6) c[0] = -1; + else if (v === 7) c[0] = 2.5; + else c[0] = 3.5; + if (v === 5) f[0] = 0.25; + else if (v === 6) f[0] = -0; + else if (v === 7) f[0] = 1e300; + else f[0] = NaN; + if (v === 5) i[0] = 128; + else if (v === 6) i[0] = -129; + else if (v === 7) i[0] = -128; + else i[0] = 127; +} +for (const v of [5, 6, 7, 8, NaN]) { + const u8 = new Uint8Array(1); + const c = new Uint8ClampedArray(1); + const f = new Float64Array(1); + const i = new Int8Array(1); + convert(u8, c, f, i, v); + console.log("convert", v, u8[0], c[0], Object.is(f[0], -0) ? "-0" : f[0], i[0]); +} + +// An index past the end ignores the store, whatever the chain chooses. +const short = new Uint8Array(2); +for (let k = 0; k < 4; k++) { + const v = k + 1; + if (v === 1) short[k] = 11; + else if (v === 2) short[k] = 22; + else if (v === 3) short[k] = 33; + else short[k] = 44; +} +console.log("short", short.join(",")); + +// Chains that keep their arms: an arm with a second statement, no final +// else, a different index, and a subject that is not a local. +let calls = 0; +function count(): number { + calls++; + return 9; +} +const kept = new Uint8Array(8); +for (let k = 0; k < 8; k++) { + const v = k % 5; + if (v === 0) kept[k] = 1; + else if (v === 1) { + kept[k] = 2; + calls += 100; + } else if (v === 2) kept[k] = 3; + else kept[k] = 4; + if (v === 0) kept[0] += 1; + else if (v === 1) kept[0] += 2; + else if (v === 2) kept[0] += 3; + if (count() === v) kept[k] = 7; + else if (v === 3) kept[(k + 1) % 8] = 5; + else if (v === 4) kept[k] = 6; + else kept[k] = kept[k]!; +} +console.log("kept", kept.join(","), calls); diff --git a/tests/harness/corpus-costs/constant-choice-chains.ts.json b/tests/harness/corpus-costs/constant-choice-chains.ts.json new file mode 100644 index 0000000000..6562c9db30 --- /dev/null +++ b/tests/harness/corpus-costs/constant-choice-chains.ts.json @@ -0,0 +1 @@ +{ "plain": 3921, "san": 5592 } From 60bd97474473287a40b72db907801b21c35fe4b4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 01:10:20 -0500 Subject: [PATCH 43/49] Wait for the lookup connection's accept in 7515 before changing the server's mode 7515-net-socket-backpressure's server picks each connection's handling from `mode` when it accepts it. The lookup client's 70000 bytes fit in the kernel's buffers, so its 'drain', and the step that sets `mode` to "pause", can run before the server accepts that connection. On macOS the accept can come in a later kqueue batch: the server then paused the lookup socket, never read its EOF, and the open connection kept the program running after server.close(). The program now waits for that accept before it changes `mode`. Its output is unchanged. --- tests/corpus/7515-net-socket-backpressure.ts | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/corpus/7515-net-socket-backpressure.ts b/tests/corpus/7515-net-socket-backpressure.ts index 260726a153..6524e6d545 100644 --- a/tests/corpus/7515-net-socket-backpressure.ts +++ b/tests/corpus/7515-net-socket-backpressure.ts @@ -7,6 +7,11 @@ // untyped code reads the same properties, and a write whose send fails // after the peer reset the connection answers false. An echo client that // waits for 'drain' when write answers false moves every byte. +// +// The server picks each connection's handling from `mode` when it accepts +// it. The lookup client's bytes fit in the kernel's buffers, so its 'drain' +// can come before the server accepts that connection; the program waits for +// the accept before it changes `mode`. import { connect, createServer, Socket } from "node:net"; import type { LookupAddress, LookupAllOptions, LookupOneOptions } from "node:dns"; @@ -113,6 +118,15 @@ function afterReset(port: number): Promise { let paused: Socket | null = null; let mode = "echo"; +let accepted: (() => void) | null = null; + +/** Resolves when the server accepts its next connection. */ +function nextConnection(): Promise { + return new Promise((resolve) => { + accepted = resolve; + }); +} + const server = createServer((socket: Socket) => { if (mode === "echo") socket.pipe(socket); else if (mode === "drain") socket.resume(); @@ -120,6 +134,11 @@ const server = createServer((socket: Socket) => { socket.pause(); paused = socket; } else socket.destroy(); + if (accepted !== null) { + const resolve = accepted; + accepted = null; + resolve(); + } }); server.listen(0, "127.0.0.1", async () => { @@ -127,7 +146,9 @@ server.listen(0, "127.0.0.1", async () => { const port = typeof addr === "object" && addr !== null ? addr.port : 0; await echo(port); mode = "drain"; + const lookupAccepted = nextConnection(); await lookupPending(port); + await lookupAccepted; mode = "pause"; await largeWrite(port); mode = "end"; From 5d62d5a5f16625046bc1bb5a641612520efa6a24 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 01:47:16 -0500 Subject: [PATCH 44/49] Read a constant choice's index as on every path when it is the compared local The store a constant choice emits for its arms used the first arm's index expression, with the ranges proven inside that arm. When the index is the compared local, `j === 0` narrows it to [0, 0] there, so the one store, which also runs on the final else's path, converted the index to an int32 from that range. For `j` = 4294967297 that conversion has no defined result, and the bounds check that follows could accept it: an out-of-bounds write could reach a valid element. The store now reads such an index through the first comparison's operand: the same local, read before the chain branches, so its range holds on every path. An index that is another local is not narrowed by the comparisons and is unchanged. --- .../src/backend/llvm/constant-choices.ts | 11 +++- .../test/constant-choice-emission.test.ts | 64 +++++++++++++++++++ tests/corpus/constant-choice-chains.ts | 14 ++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/constant-choice-emission.test.ts diff --git a/packages/compiler/src/backend/llvm/constant-choices.ts b/packages/compiler/src/backend/llvm/constant-choices.ts index 208fac36aa..6e7bb4c84d 100644 --- a/packages/compiler/src/backend/llvm/constant-choices.ts +++ b/packages/compiler/src/backend/llvm/constant-choices.ts @@ -218,8 +218,13 @@ export function emitConstantChoice(host: LlvmEmitterContext, choice: ConstantCho const first = choice.stores[0]!; if (first.arr.type.kind !== "bytes") throw new InternalCompilerError("llvm emitter bug: constant choice on non-bytes"); - const arr = host.emitStableReceiver(first.arr, [first.index]); - const index = host.emitExpr(first.index); + // An arm's index carries the ranges proven inside that arm: an index that + // is the compared local holds the arm's value there. The one store runs + // for every value, so such an index reads the local through the first + // comparison's operand, whose range holds before the chain branches. + const indexExpr = sameVarRef(first.index, choice.subject) ? choice.subject : first.index; + const arr = host.emitStableReceiver(first.arr, [indexExpr]); + const index = host.emitExpr(indexExpr); const value = emitChoice(host, choice); const scopes = choice.stores.map((store) => host.bytesAliasScopes.get(store)); host.emitBytesSet( @@ -227,7 +232,7 @@ export function emitConstantChoice(host: LlvmEmitterContext, choice: ConstantCho arr.name, index, value, - first.index, + indexExpr, undefined, choice.stores.every((store) => host.bytesBounds.has(store)), choice.stores.every((store) => host.bytesOwned.has(store)), diff --git a/packages/compiler/test/constant-choice-emission.test.ts b/packages/compiler/test/constant-choice-emission.test.ts new file mode 100644 index 0000000000..bbc54001ae --- /dev/null +++ b/packages/compiler/test/constant-choice-emission.test.ts @@ -0,0 +1,64 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; + +test("a chain of literal stores is one store of a table entry, indexed as on every path", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-constant-choices-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + ` +function bases(arr: Uint8Array): void { + for (let i = 0; i < arr.length; ++i) { + const x = arr[i]; + if (x === 97) arr[i] = 0; + else if (x === 116) arr[i] = 1; + else if (x === 99) arr[i] = 2; + else arr[i] = 3; + } +} +function wide(flag: boolean): string { + const a = new Uint8Array(4); + let j = 0; + if (flag) j = 4294967297; + if (j === 0) a[j] = 11; + else if (j === 1) a[j] = 22; + else if (j === 2) a[j] = 33; + else a[j] = 44; + return a.join(","); +} +const sequence = new Uint8Array([97, 116, 99, 103]); +bases(sequence); +console.log(sequence.join(","), wide(true), wide(false)); +`, + ); + const outPath = join(dir, "main.ir.json"); + const lowered = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!lowered.ok) + throw new Error(lowered.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ll = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + const body = (name: string): string => + ll.match( + new RegExp(`define internal [^\\n]+ @sc_[bf]+_${name}\\([^\\n]*\\) #0 \\{[\\s\\S]*?\\n\\}`), + )![0]; + + // The cases' span, 97 to 116, with the final else's 3 between the cases. + expect(ll).toMatch( + /@sc_choice_\w+ = private unnamed_addr constant \[20 x i32\] \[i32 0, i32 3, i32 2, (i32 3, ){16}i32 1\]/, + ); + expect(body("bases")).toMatch(/load i32, ptr %\S+\n/); + + // The index is the compared local. Inside the first arm its range is + // [0, 0], but the one store also runs for 4294967297, so the index + // converts as the local's whole range allows (to i64), not to i32. + const wide = body("wide"); + expect(wide).toMatch(/@sc_choice_\w+, i64 0, i64/); + expect(wide).not.toMatch(/fptosi double %\S+ to i32/); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/tests/corpus/constant-choice-chains.ts b/tests/corpus/constant-choice-chains.ts index 8d749daee4..5cbae7d8a2 100644 --- a/tests/corpus/constant-choice-chains.ts +++ b/tests/corpus/constant-choice-chains.ts @@ -72,6 +72,20 @@ for (let k = 0; k < 4; k++) { } console.log("short", short.join(",")); +// The index is the compared local, and on the final else's path it is +// past the end, also past 2^32: the store is ignored there. +function indexed(flag: boolean, far: boolean): string { + const a = new Uint8Array(4); + let j = 0; + if (flag) j = far ? 4294967297 : 3; + if (j === 0) a[j] = 11; + else if (j === 1) a[j] = 22; + else if (j === 2) a[j] = 33; + else a[j] = 44; + return a.join(","); +} +console.log("indexed", indexed(false, false), indexed(true, false), indexed(true, true)); + // Chains that keep their arms: an arm with a second statement, no final // else, a different index, and a subject that is not a local. let calls = 0; From 055d1f9682f43453276b2bd0b6fbfdca1066e484 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 01:50:59 -0500 Subject: [PATCH 45/49] Fix eight compiler and type bug reports Co-authored-by: dolphin278 <1130473+dolphin278@users.noreply.github.com> Fixes #2139 Fixes #2131 Fixes #2132 Fixes #2133 Fixes #2140 Fixes #2141 Fixes #2159 --- .../ambient/scriptc-node-fallback.d.ts | 10 +++ .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/frontend/lowering/array-values.ts | 3 + .../frontend/lowering/builtins/filesystem.ts | 87 ++++++++++++++++++- .../lowering/expressions/element-access.ts | 16 ++++ .../lowering/expressions/object-literals.ts | 6 +- .../lowering/expressions/property-access.ts | 12 +++ .../lowering/expressions/union-fields.ts | 55 ++++++++++++ .../compiler/src/frontend/lowering/lowerer.ts | 53 ++++++++++- .../src/frontend/lowering/surfaces.ts | 4 +- .../src/frontend/lowering/typed-arguments.ts | 11 ++- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 3 + .../tests/corpus/fs-readdir-recursive.ts.json | 6 ++ .../never-narrowed-literal-argument.ts.json | 6 ++ .../rest-tuple-shorthand-property.ts.json | 6 ++ .../string-plus-element-read-return.ts.json | 6 ++ .../symbol-keyed-recursive-record.ts.json | 6 ++ .../corpus/union-tuple-element-read.ts.json | 6 ++ .../promise-all-generic-mixed-union.ts.json | 6 ++ .../untyped-map-into-interface.ts.json | 6 ++ packages/runtime/src/scr_lib.c | 50 +++++++++++ packages/runtime/src/scr_lib.h | 1 + tests/corpus/fs-readdir-recursive.ts | 36 ++++++++ .../corpus/never-narrowed-literal-argument.ts | 46 ++++++++++ tests/corpus/rest-tuple-shorthand-property.ts | 23 +++++ .../corpus/string-plus-element-read-return.ts | 27 ++++++ tests/corpus/symbol-keyed-recursive-record.ts | 35 ++++++++ tests/corpus/union-tuple-element-read.ts | 45 ++++++++++ .../promise-all-generic-mixed-union.ts | 8 ++ .../diagnostics/untyped-map-into-interface.ts | 12 +++ .../promise-all-generic-mixed-union.ts.txt | 8 ++ .../untyped-map-into-interface.ts.txt | 8 ++ .../corpus-costs/fs-readdir-recursive.ts.json | 1 + .../never-narrowed-literal-argument.ts.json | 1 + .../rest-tuple-shorthand-property.ts.json | 1 + .../string-plus-element-read-return.ts.json | 1 + .../symbol-keyed-recursive-record.ts.json | 1 + .../union-tuple-element-read.ts.json | 1 + 39 files changed, 608 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/fs-readdir-recursive.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/never-narrowed-literal-argument.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/rest-tuple-shorthand-property.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/string-plus-element-read-return.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-keyed-recursive-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-tuple-element-read.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/promise-all-generic-mixed-union.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/untyped-map-into-interface.ts.json create mode 100644 tests/corpus/fs-readdir-recursive.ts create mode 100644 tests/corpus/never-narrowed-literal-argument.ts create mode 100644 tests/corpus/rest-tuple-shorthand-property.ts create mode 100644 tests/corpus/string-plus-element-read-return.ts create mode 100644 tests/corpus/symbol-keyed-recursive-record.ts create mode 100644 tests/corpus/union-tuple-element-read.ts create mode 100644 tests/diagnostics/promise-all-generic-mixed-union.ts create mode 100644 tests/diagnostics/untyped-map-into-interface.ts create mode 100644 tests/harness/__snapshots__/promise-all-generic-mixed-union.ts.txt create mode 100644 tests/harness/__snapshots__/untyped-map-into-interface.ts.txt create mode 100644 tests/harness/corpus-costs/fs-readdir-recursive.ts.json create mode 100644 tests/harness/corpus-costs/never-narrowed-literal-argument.ts.json create mode 100644 tests/harness/corpus-costs/rest-tuple-shorthand-property.ts.json create mode 100644 tests/harness/corpus-costs/string-plus-element-read-return.ts.json create mode 100644 tests/harness/corpus-costs/symbol-keyed-recursive-record.ts.json create mode 100644 tests/harness/corpus-costs/union-tuple-element-read.ts.json diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index ee2208bd90..79305a8ba9 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -1718,6 +1718,16 @@ declare module "node:fs" { ): void; export function rmdirSync(path: string | URL): void; export function readdirSync(path: string | URL): string[]; + /* The recursive form: every entry below the path, relative to it. */ + export function readdirSync( + path: string | URL, + options: { + recursive?: boolean; + withFileTypes?: false; + encoding?: "utf8" | "utf-8"; + [option: string]: unknown; + }, + ): string[]; /* The withFileTypes form: Dirent rows (name + parentPath + the type * probes — the honest subset of Node's Dirent surface). The type is * interned explicitly in type-mapper.ts (the record's hidden %dtype field diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 0e80eee900..182ac49aba 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -423,6 +423,7 @@ export const LIB_FN_SYMS: Record = { "fs.rmRetrySync": "scr_fs_rm_opts_retry", "fs.rmdirSync": "scr_fs_rmdir", "fs.readdirSync": "scr_fs_readdir", + "fs.readdirRecursiveSync": "scr_fs_readdir_recursive", "fs.globSync": "scr_fs_glob_strings", "fs.callbackGlob": "scr_fs_glob_callback", "fsp.globStrings": "scr_fsp_glob_strings", diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index 8fbb77bae5..a8c2d699b0 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -692,6 +692,9 @@ export function lowerSafeIndexRead( const elem = arr.type.elem; if (elem.kind === "void") return null; const resultT = arrayValueType(lowerer, elem); + // An element union that cannot take an undefined arm has no value for a + // missing element. + if (resultT.kind !== "dyn" && lowerer.wrappedUndefined(resultT, loc) === null) return null; const key = `idxOr:${typeKey(elem)}`; let name = lowerer.arrHofHelpers.get(key); if (!name) { diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts index 6b988c491d..3b32fa29b3 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts @@ -1306,6 +1306,7 @@ export function lowerFsReaddirTypesCall( ); } let sawWithFileTypes = false; + let recursive: ts.Expression | null = null; for (const p of optsNode.properties.nodes) { const m = optionMember(p); if (!m) { @@ -1334,9 +1335,11 @@ export function lowerFsReaddirTypesCall( "names decode as utf8 (the default); encoding: 'buffer' has no lowering", ); } + } else if (m.name === "recursive" && !promiseForm) { + recursive = m.value; } else { // The options-record stance: recursive (a documented knob with - // no lowering) fences by name; undocumented keys drop like Node. + // no lowering here) fences by name; undocumented keys drop like Node. fenceOrDropOptionKey( lowerer, p, @@ -1347,6 +1350,19 @@ export function lowerFsReaddirTypesCall( ); } } + if (recursive !== null) { + const flag = lowerer.typeOf(recursive); + const literal = + flag.flags & ts.TypeFlags.BooleanLiteral ? lowerer.checker.typeToString(flag) : null; + if (sawWithFileTypes && literal !== "false") { + lowerer.noLowering( + `${operation} with withFileTypes and recursive`, + recursive, + "recursive: true lists relative names; withFileTypes: true lists one directory's Dirents", + ); + } + if (!sawWithFileTypes) return lowerFsReaddirRecursive(lowerer, call, recursive, literal, loc); + } if (!sawWithFileTypes) { lowerer.noLowering( `${operation} with 2 arguments`, @@ -1388,6 +1404,75 @@ export function lowerFsReaddirTypesCall( }; } +/** `fs.readdirSync(path, { recursive })` without withFileTypes: the + * relative names of every entry below the path when `recursive` is true + * (scr_fs_readdir_recursive, Node's queue order), the directory's own names + * when it is false. A recursive flag that is not a boolean literal selects + * between the two at run time, after the path is evaluated. */ +function lowerFsReaddirRecursive( + lowerer: Lowerer, + call: ts.CallExpression, + recursive: ts.Expression, + literal: string | null, + loc: SrcLoc, +): IrExpr { + const names = arrayOf(STRING); + // @types/node types the call `string[] | Buffer[]` when the options name + // no encoding; the names are strings, so the call is the string[] arm. + const callType = lowerer.mapTypeOf(lowerer.typeOf(call)); + if ( + !callType || + !( + typeEquals(callType, names) || + (callType.kind === "union" && lowerer.armTag(callType.unionId, names) >= 0) + ) + ) { + lowerer.noLowering( + "readdirSync(path, { recursive }) where the result is not a string array", + call, + "names decode as utf8 strings (the default encoding)", + ); + } + return lowerFsReaddirNames(lowerer, call, recursive, literal, names, loc); +} + +function lowerFsReaddirNames( + lowerer: Lowerer, + call: ts.CallExpression, + recursive: ts.Expression, + literal: string | null, + names: IrType, + loc: SrcLoc, +): IrExpr { + const path = lowerFsPath(lowerer, call.arguments.nodes[0]!); + const listing = (fn: IrLibFn, arg: IrExpr): IrExpr => ({ + kind: "libCall", + fn, + args: [arg], + type: names, + loc, + }); + if (literal === "true") return listing("fs.readdirRecursiveSync", path); + if (literal === "false") return listing("fs.readdirSync", path); + const pathLocal = lowerer.declareHiddenLocal("%readdirPath", STRING); + const pathRef = varRef(pathLocal.id, STRING, loc); + const flag = lowerer.lowerExprExpecting(recursive, BOOL); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: pathLocal.id, init: path, loc }], + result: { + kind: "ternary", + cond: flag, + then: listing("fs.readdirRecursiveSync", pathRef), + else_: listing("fs.readdirSync", pathRef), + type: names, + loc, + }, + type: names, + loc, + }; +} + /** fs.statSync / fs.lstatSync's documented option keys. */ const FS_STAT_DOCUMENTED_OPTIONS: ReadonlySet = new Set(["bigint", "throwIfNoEntry"]); diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 8e60b33290..bbeeb78918 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -1,5 +1,6 @@ /* Element reads and writes (`a[i]`, `a[i] = v`), compound element updates, and keyed record reads. */ import { OBJECT_PROTOTYPE_MEMBERS } from "../../shape-inference/domain.js"; +import { lowerUnionTupleRead } from "./union-fields.js"; import * as ts from "../../ts7/adapter.js"; import { lowerOptionalIndex, @@ -466,6 +467,12 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre "tuple indexing with a non-literal index (tuples are fixed-shape — use t[0], t[1], ...)", ); } + // A union of tuples asserted to one tuple view (`(r as readonly + // [boolean, T | E])[0]`) still holds the union. + if (obj.type.kind === "union") { + const element = lowerUnionTupleRead(lowerer, obj, idx, locOf(expr)); + if (element) return lowerer.maybeNarrow(element, expr); + } const shapeId = obj.type.kind === "record" ? obj.type.shapeId : receiverIr.shapeId; const actualShape = lowerer.shapes.get(shapeId) ?? shape; const fieldType = actualShape.fields.find((f) => f.name === String(idx))?.type; @@ -598,6 +605,15 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre } } } + if (key.type.kind === "f64" && ts.isNumericLiteral(expr.argumentExpression)) { + const element = lowerUnionTupleRead( + lowerer, + value, + Number(expr.argumentExpression.text), + locOf(expr), + ); + if (element) return lowerer.maybeNarrow(element, expr); + } if (key.type.kind === "f64") { const optional = optionalArmIndexRead(lowerer, value, key, locOf(expr)); if (optional) return lowerer.maybeNarrow(optional, expr); diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index af974c2eb9..31479e4b01 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -419,7 +419,11 @@ export function lowerDynObjectLiteral( : !boxValue && (ts.isObjectLiteralExpression(valueExpr) || (ts.isArrayLiteralExpression(valueExpr) && - (holdsEvolvingArray(lowerer, valueExpr) || + // An empty literal takes whatever the object's users + // store into it (`{ [kChildren]: [] }`), so it is a + // checked-dynamic array rather than its `never[]` layout. + (valueExpr.elements.nodes.length === 0 || + holdsEvolvingArray(lowerer, valueExpr) || lowerer.untypedLiteralContext(valueExpr)))) ? lowerer.lowerExprExpecting(valueExpr, DYN) : lowerer.lowerExpr(valueExpr as ts.Expression); diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 6bdf9e2f05..0c4264f461 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -3376,6 +3376,18 @@ export function lowerUnionProperty( expr, ); } + // An array the checker sees through a union of arrays (`readdirSync`'s + // `string[] | Buffer[]`, whose lowering answers the string[] arm). + if (value.type.kind === "array" && expr.name.text === "length") { + return { + kind: "arrIntrinsic", + method: "length", + receiver: value, + args: [], + type: F64, + loc: locOf(expr), + }; + } if (value.type.kind !== "union") { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts index 4f4532a6a9..01c841fdd3 100644 --- a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts +++ b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts @@ -281,3 +281,58 @@ export function lowerUnionFieldWrite( } return { kind: "seqExpr", stmts: body, result: dispatch, type: result.type, loc }; } + +/** Read the element at a literal index from a union of tuples whose element + * types differ (`readonly [true, number] | readonly [false, string]`, index + * 1): each tag reads its own tuple's slot and widens it into the union of + * the arms' element types; the caller narrows the result to the checker's + * flow type. Null when an arm is not a tuple with that position. */ +export function lowerUnionTupleRead( + lowerer: Lowerer, + receiver: IrExpr, + index: number, + loc: IrExpr["loc"], +): IrExpr | null { + if (receiver.type.kind !== "union" || !Number.isInteger(index) || index < 0) return null; + const unionId = receiver.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms; + if (!arms || arms.length < 2) return null; + const name = String(index); + const fields: UnionFieldArm[] = []; + const parts: IrType[] = []; + for (const arm of arms) { + if (arm.kind !== "record" || lowerer.shapes.get(arm.shapeId)?.tuple !== true) return null; + const field = unionFieldArm(lowerer, arm, name); + if (!field || field.type.kind === "void") return null; + fields.push(field); + const own = + field.type.kind === "union" ? lowerer.unions.get(field.type.unionId)?.arms : [field.type]; + if (!own) return null; + for (const part of own) if (!parts.some((p) => typeEquals(p, part))) parts.push(part); + } + const type: IrType = + parts.length === 1 ? parts[0]! : { kind: "union", unionId: lowerer.unions.intern(parts) }; + const object = lowerer.declareHiddenLocal("%unionReadObject", receiver.type); + const objectRef = varRef(object.id, object.type, loc); + const read = (tag: number): IrExpr | null => { + const probe = unionFieldRead(unionId, tag, objectRef, fields[tag]!, name, loc); + const converted = lowerer.coerceToExpected(probe, type); + return typeEquals(converted.type, type) && isWidening(converted, probe) ? converted : null; + }; + let dispatch = read(fields.length - 1); + if (!dispatch) return null; + for (let tag = fields.length - 2; tag >= 0; tag--) { + const then = read(tag); + if (!then) return null; + dispatch = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, value: objectRef, negated: false, type: BOOL, loc }, + then, + else_: dispatch, + type, + loc, + }; + } + const init: IrStmt = { kind: "varDecl", localId: object.id, init: receiver, loc }; + return { kind: "seqExpr", stmts: [init], result: dispatch, type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 10ddee75d3..3f3f6ea336 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { unionNarrowFailureMessage } from "./union-narrow.js"; import { compactDeclOrigins, inPackageFile, @@ -10,7 +11,7 @@ import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js"; import { keyedHelperName } from "./stable-names.js"; import { positionTag, StructuralPositions } from "./structural-positions.js"; -import { onlyForwarded, stripTypeWrappers } from "./typed-arguments.js"; +import { bindingSymbolOfRead, onlyForwarded, stripTypeWrappers } from "./typed-arguments.js"; import { needsGeneralNodeStreams, noteUntypedStreamFlow, @@ -6008,6 +6009,15 @@ export class Lowerer { }; if (!stringRead(e.left) && !stringRead(e.right)) return null; if (!primitive(e.left) || !primitive(e.right)) return null; + // A missing read is undefined, and `+` yields a number only when + // neither operand is a string: an operand that is always a string + // (`"x-" + PAD[i]`) makes the result a string. A read, or a sum or + // call whose result may itself be NaN, is not always a string. + const definitelyString = (part: ts.Expression): boolean => + !stringRead(part) && + this.mapTypeOf(this.typeOf(part))?.kind === "string" && + optionalPrimitiveResultType(part) === null; + if (definitelyString(e.left) || definitelyString(e.right)) return null; return { kind: "union", unionId: this.unions.intern([F64, STRING]) }; }; /** The union of primitive result types, or null when any is missing or @@ -11295,6 +11305,26 @@ export class Lowerer { private coerceIntoSlot(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { refuseInferredCallbackDrops(this, node, expr.type, expected); + // A union into one of its record arms extracts that arm, trusting the + // checker to have excluded the others. A built-in object arm the checker + // still types the site with (`c ?? new Map()` into an interface the + // untyped Map satisfies only through `any`) holds a value at run time, + // and no conversion makes a record of it, so the flow is refused like + // the arm's value would be. + if ( + expr.type.kind === "union" && + expected.kind === "record" && + this.armTag(expr.type.unionId, expected) >= 0 + ) { + const site = this.mapTypeOf(this.typeOf(node)); + const siteArms = + site?.kind === "union" ? (this.unions.get(site.unionId)?.arms ?? []) : site ? [site] : []; + for (const arm of this.unions.get(expr.type.unionId)?.arms ?? []) { + if (arm.kind !== "map" && arm.kind !== "set") continue; + if (!siteArms.some((s) => typeEquals(s, arm))) continue; + this.requireExactShape(node, arm, expected); + } + } // An assertion's scalar check whose value goes straight back into a // checked-dynamic slot (`process.loadEnvFile({} as unknown as string)`) // keeps the original value: JavaScript erases assertions, so the check @@ -11457,6 +11487,25 @@ export class Lowerer { } } + // A value the checker types `never` at this site (a read narrowed past + // every member of its union) exists only when an assertion lied about + // its type, so its layout need not fit the slot: it is evaluated, then a + // TypeError is thrown, as checked extraction of an impossible arm does. + if (!typeEquals(e.type, expected) && (this.typeOf(node).flags & ts.TypeFlags.Never) !== 0) { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: e, loc: e.loc }], + result: nodeThrowExpr( + 1, + "", + unionNarrowFailureMessage(e.type, (t) => this.fmt(t)), + expected, + e.loc, + ), + type: expected, + loc: e.loc, + }; + } this.requireExactShape(node, e.type, expected); return e; } @@ -12989,7 +13038,7 @@ export class Lowerer { const fn = decl.parent; const symbol = this.checker.getSymbolAtLocation(nameNode); const isRead = (id: ts.Identifier): boolean => - id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol; + id !== nameNode && bindingSymbolOfRead(this, id) === symbol; const pure = fn !== undefined && symbol !== undefined && onlyForwarded(this, fn, isRead); // A forwarded-only tuple is never built: a tuple with optional // positions (`Parameters<(n?: T) => R>`, whose length varies per call) diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index cf0428b60f..3520e3aeac 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2223,8 +2223,8 @@ export const BUILTIN_MODULE_FN_ALIASES: Record< mkdirSync: ["fs.mkdirModeSync", "fs.mkdirRecursiveSync", "fs.mkdirRecursiveModeSync"], // The { recursive, force } and maxRetries/retryDelay option lowerings. rmSync: ["fs.rmOptsSync", "fs.rmRetrySync"], - // The { withFileTypes: true } Dirent form. - readdirSync: ["fs.readdirTypesSync"], + // The { withFileTypes: true } Dirent form and the { recursive: true } walk. + readdirSync: ["fs.readdirTypesSync", "fs.readdirRecursiveSync"], // The { throwIfNoEntry } probe form. statSync: ["fs.statProbeSync"], // realpathSync and realpathSync.native read as function values. diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index e415add7cf..e837d59e6a 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -195,6 +195,15 @@ export function declareTypedArguments( }); } +/** The binding an identifier reads: its symbol, or for the name of a + * shorthand property (`{ xs }`) the variable whose value the property takes. */ +export function bindingSymbolOfRead(lowerer: Lowerer, id: ts.Identifier): ts.Symbol | undefined { + const parent = id.parent; + if (parent && ts.isShorthandPropertyAssignment(parent) && parent.name === id) + return lowerer.checker.getShorthandAssignmentValueSymbol(parent); + return lowerer.checker.getSymbolAtLocation(id); +} + /** Whether every read of a tuple binding in `fn` (identified by `isRoot`) * only forwards it: `f.apply(t, x)`, `g(...x)` into a program function, * `x.length`, or a const alias whose own reads only forward. Reads inside @@ -262,7 +271,7 @@ export function onlyForwarded( const alias = lowerer.checker.getSymbolAtLocation(p.name); const aliasName = p.name; if (alias) { - roots.push((id) => id !== aliasName && lowerer.checker.getSymbolAtLocation(id) === alias); + roots.push((id) => id !== aliasName && bindingSymbolOfRead(lowerer, id) === alias); return undefined; } } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 423219efaf..709f72d45c 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -765,6 +765,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "fs.rmSync", "fs.rmdirSync", "fs.readdirSync", + "fs.readdirRecursiveSync", "fs.readdirTypesSync", "fs.globSync", "fs.globTypesSync", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index c83ac241b5..0be10a37a6 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -592,6 +592,9 @@ function buildLibFnSignatures() { "fs.rmSync": { argTypes: [STRING], result: VOID }, "fs.rmdirSync": { argTypes: [STRING], result: VOID }, "fs.readdirSync": { argTypes: [STRING], result: arrayOf(STRING) }, + /** `fs.readdirSync(path, { recursive: true })`: every entry below the + * path, relative to it, in Node's queue order. */ + "fs.readdirRecursiveSync": { argTypes: [STRING], result: arrayOf(STRING) }, // Result is the call site's Dirent record array (VOID is the // structure-checked sentinel, the os.networkInterfaces pattern). /** `fs.readdirSync(path, { withFileTypes: true })` — Dirent rows over diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/fs-readdir-recursive.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/fs-readdir-recursive.ts.json new file mode 100644 index 0000000000..c045eadf11 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/fs-readdir-recursive.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/fs-readdir-recursive.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/never-narrowed-literal-argument.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/never-narrowed-literal-argument.ts.json new file mode 100644 index 0000000000..55b7b5d8ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/never-narrowed-literal-argument.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/never-narrowed-literal-argument.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/rest-tuple-shorthand-property.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/rest-tuple-shorthand-property.ts.json new file mode 100644 index 0000000000..ca2d5289c6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/rest-tuple-shorthand-property.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/rest-tuple-shorthand-property.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/string-plus-element-read-return.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/string-plus-element-read-return.ts.json new file mode 100644 index 0000000000..1e78a18024 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/string-plus-element-read-return.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/string-plus-element-read-return.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-keyed-recursive-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-keyed-recursive-record.ts.json new file mode 100644 index 0000000000..e46feaab27 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-keyed-recursive-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/symbol-keyed-recursive-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-tuple-element-read.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-tuple-element-read.ts.json new file mode 100644 index 0000000000..bf23de474a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-tuple-element-read.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-tuple-element-read.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/promise-all-generic-mixed-union.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/promise-all-generic-mixed-union.ts.json new file mode 100644 index 0000000000..2732b9539c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/promise-all-generic-mixed-union.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/promise-all-generic-mixed-union.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/untyped-map-into-interface.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/untyped-map-into-interface.ts.json new file mode 100644 index 0000000000..1d39d436fd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/untyped-map-into-interface.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/untyped-map-into-interface.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 4cfda127be..928400da3c 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -6344,6 +6344,56 @@ ScrArr *scr_fs_readdir(ScrStr *path) { return arr; } +/* fs.readdirSync(path, { recursive: true }): Node's readdirSyncRecursive. + * Directories are listed in queue order: the base, then each directory in + * the order its entry was listed. Each entry's path is path.join of its + * directory and name; the result holds it relative to the base + * (path.relative). An entry stat(2) reports as a directory, a symlink to + * one included, joins the queue. */ +ScrArr *scr_fs_readdir_recursive(ScrStr *base) { + if (!scr_fs_path_ok(base, "path")) return NULL; + ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0); + size_t len = 1, cap = 8; + ScrStr **queue = malloc(cap * sizeof *queue); + if (!queue) scr_trap("scriptc: out of memory\n"); + queue[0] = scr_str_retain(base); + bool failed = false; + for (size_t at = 0; at < len && !failed; at++) { + ScrScandir *scan = scr_fs_scandir(queue[at]); + if (!scan) { + failed = true; + break; + } + for (size_t i = 0; i < scr_fs_scandir_count(scan); i++) { + ScrArr *parts = scr_arr_new(SCR_ELEM_STR, 2); + scr_arr_push_ref(parts, scr_str_retain(queue[at])); + scr_arr_push_ref(parts, scr_fs_scandir_name(scan, i)); + ScrStr *full = scr_path_join(parts); + scr_arr_release(parts); + scr_arr_push_ref(out, scr_path_relative(base, full)); + struct stat st; + if (stat(full->data, &st) == 0 && S_ISDIR(st.st_mode)) { + if (len == cap) { + cap *= 2; + queue = realloc(queue, cap * sizeof *queue); + if (!queue) scr_trap("scriptc: out of memory\n"); + } + queue[len++] = full; + } else { + scr_str_release(full); + } + } + scr_fs_scandir_free(scan); + } + for (size_t i = 0; i < len; i++) scr_str_release(queue[i]); + free(queue); + if (failed) { + scr_arr_release(out); + return NULL; + } + return out; +} + /* ── the withFileTypes scandir snapshot ────────────────────────────── * One readdir pass capturing name + entry kind for the emitter's Dirent * assembly (scr_runtime.h has the contract). Kinds are libuv's UV_DIRENT diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index 7ba9e2907b..e01bc7b236 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -293,6 +293,7 @@ void scr_fs_mkdir(ScrStr *path); void scr_fs_rm(ScrStr *path); void scr_fs_rmdir(ScrStr *path); ScrArr *scr_fs_readdir(ScrStr *path); /* names (byte order on POSIX, as libuv sorts), no "."/".." */ +ScrArr *scr_fs_readdir_recursive(ScrStr *path); /* relative paths of every entry below path, Node's queue order */ /* The fs option forms (same throw discipline). mkdir_recursive is Node's * recursive algorithm (existing-dir target is a no-op; errors report diff --git a/tests/corpus/fs-readdir-recursive.ts b/tests/corpus/fs-readdir-recursive.ts new file mode 100644 index 0000000000..310cd3b52e --- /dev/null +++ b/tests/corpus/fs-readdir-recursive.ts @@ -0,0 +1,36 @@ +// readdirSync(path, { recursive: true }) lists every entry below the path, +// relative to it: the path's own entries first, then each directory's in the +// order its entry was listed. A symlink to a directory is followed. + +import { mkdirSync, mkdtempSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const root = mkdtempSync(join(tmpdir(), "readdir-recursive-")); +writeFileSync(join(root, "a.txt"), "a"); +mkdirSync(join(root, "b", "d"), { recursive: true }); +writeFileSync(join(root, "b", "c.txt"), "c"); +writeFileSync(join(root, "b", "d", "e.txt"), "e"); +mkdirSync(join(root, "empty")); +mkdirSync(join(root, "z")); +writeFileSync(join(root, "z", "y.txt"), "y"); +symlinkSync(join(root, "b", "d"), join(root, "link")); + +console.log(readdirSync(root, { recursive: true }).join(" ")); +console.log(readdirSync(root, { recursive: false }).join(" ")); +console.log(readdirSync(`${root}/b/`, { recursive: true }).join(" ")); + +let deep = false; +for (const flag of [false, true]) { + deep = flag; + console.log(deep, readdirSync(join(root, "z"), { recursive: deep }).length); +} +console.log(readdirSync(join(root, "empty"), { recursive: true }).length); + +try { + readdirSync(join(root, "missing"), { recursive: true }); +} catch (e) { + console.log((e as NodeJS.ErrnoException).code); +} + +rmSync(root, { recursive: true }); diff --git a/tests/corpus/never-narrowed-literal-argument.ts b/tests/corpus/never-narrowed-literal-argument.ts new file mode 100644 index 0000000000..a9bb5489fc --- /dev/null +++ b/tests/corpus/never-narrowed-literal-argument.ts @@ -0,0 +1,46 @@ +// A string-literal union narrowed to `never` passes to a `never` parameter: +// the exhaustiveness-check idiom over if-chains and switches compiles, and the +// unreachable call never runs. + +function unreachable(_: never): never { + throw new Error("unreachable"); +} + +type Kind = "a" | "b"; +function viaIf(kind: Kind): number { + if (kind === "a") return 1; + if (kind === "b") return 2; + return unreachable(kind); +} + +type Shape = "circle" | "square" | "triangle"; +function corners(shape: Shape): number { + switch (shape) { + case "circle": + return 0; + case "square": + return 4; + case "triangle": + return 3; + default: + return unreachable(shape); + } +} + +function asserted(kind: Kind): string { + if (kind === "a" || kind === "b") return kind.toUpperCase(); + return unreachable(kind as never); +} + +type Level = 1 | 2; +function viaNumbers(level: Level): string { + if (level === 1) return "one"; + if (level === 2) return "two"; + return unreachable(level); +} + +const kinds: Kind[] = ["a", "b"]; +console.log(kinds.map(viaIf).join(","), viaIf(Date.now() > 0 ? "a" : "b")); +const shapes: Shape[] = ["circle", "square", "triangle"]; +console.log(shapes.map(corners).join(",")); +console.log(kinds.map(asserted).join(""), viaNumbers(1), viaNumbers(2)); diff --git a/tests/corpus/rest-tuple-shorthand-property.ts b/tests/corpus/rest-tuple-shorthand-property.ts new file mode 100644 index 0000000000..d67afa443f --- /dev/null +++ b/tests/corpus/rest-tuple-shorthand-property.ts @@ -0,0 +1,23 @@ +// A rest parameter typed as a fixed tuple, read through a shorthand property +// (`{ xs }`), directly or through a const alias, is the tuple of the call's +// arguments. + +function wrap(...xs: [number]) { + return { xs }; +} +function pair(...args: [string, number]) { + const kept = args; + return { kept, size: args.length }; +} +function add(a: number, b: number): number { + return a + b; +} +function forward(...args: [number, number]): number { + return add(...args); +} + +const w = wrap(1); +console.log(w.xs[0], w.xs.length, JSON.stringify(w)); +const p = pair("a", 2); +console.log(p.kept[0], p.kept[1], p.size, JSON.stringify(p)); +console.log(forward(3, 4)); diff --git a/tests/corpus/string-plus-element-read-return.ts b/tests/corpus/string-plus-element-read-return.ts new file mode 100644 index 0000000000..f4051f6153 --- /dev/null +++ b/tests/corpus/string-plus-element-read-return.ts @@ -0,0 +1,27 @@ +// A function declared to return a string concatenates a string with an +// element of a readonly string array. A missing element reads as undefined, +// which `+` turns into "undefined" next to a string, so the result is always +// a string and its call can be used inline. + +const PAD: readonly string[] = ["00", "01", "02"]; + +function label(i: number): string { + return "x-" + PAD[i]; +} +function date(y: number, m: number, d: number): string { + return y + "-" + PAD[m] + "-" + PAD[d]; +} +function suffix(i: number): string { + return PAD[i] + "!"; +} + +console.log(label(1).length, label(1), label(7)); +console.log(date(2026, 1, 2).length, date(2026, 2, 9)); +console.log(suffix(0).toUpperCase(), suffix(5)); + +// Two possibly missing reads add to NaN when both are missing. +function pair(i: number, j: number): string { + return PAD[i] + PAD[j]; +} +const both = pair(8, 9); +console.log(pair(0, 1), typeof both, String(both)); diff --git a/tests/corpus/symbol-keyed-recursive-record.ts b/tests/corpus/symbol-keyed-recursive-record.ts new file mode 100644 index 0000000000..c26d7282e5 --- /dev/null +++ b/tests/corpus/symbol-keyed-recursive-record.ts @@ -0,0 +1,35 @@ +// A recursive record keyed by unique symbols, built from literals whose +// child arrays start empty, holds the nodes later pushed into those arrays. + +const kValue: unique symbol = Symbol("value"); +const kChildren: unique symbol = Symbol("children"); + +interface TreeNode { + [kChildren]: TreeNode[]; + [kValue]: number; +} + +function node(value: number): TreeNode { + return { [kValue]: value, [kChildren]: [] }; +} + +const root = node(0); +root[kChildren].push(node(1)); +root[kChildren].push(node(2)); +root[kChildren][0]![kChildren].push(node(3)); +console.log(root[kChildren].length, root[kChildren][0]![kValue], root[kChildren][1]![kValue]); + +function sum(n: TreeNode): number { + let total = n[kValue]; + for (const child of n[kChildren]) total += sum(child); + return total; +} +function depth(n: TreeNode): number { + return 1 + Math.max(0, ...n[kChildren].map(depth)); +} +console.log(sum(root), depth(root)); + +// A literal that starts with children. +const seeded: TreeNode = { [kValue]: 10, [kChildren]: [node(11)] }; +seeded[kChildren].push(node(12)); +console.log(seeded[kChildren].map((c) => c[kValue]).join(","), sum(seeded)); diff --git a/tests/corpus/union-tuple-element-read.ts b/tests/corpus/union-tuple-element-read.ts new file mode 100644 index 0000000000..141609d2b6 --- /dev/null +++ b/tests/corpus/union-tuple-element-read.ts @@ -0,0 +1,45 @@ +// Literal-index reads from a union of tuples whose element types differ: +// each tuple's own element is read, and the checker's narrowing of the +// tuple (or of the element) applies as usual. + +type Ok = readonly [true, T]; +type Err = readonly [false, E]; +type Result = Ok | Err; + +function pick(n: number): Result { + return n > 0 ? [true, n] : [false, "neg"]; +} + +for (const n of [1, -1, 5]) { + const r = pick(n); + console.log(r[0], r[1]); + if (r[0]) console.log(r[1] + 1); + else console.log(r[1].length); +} + +function describe(r: Result): string { + const value = r[1]; + return `${r[0] ? "ok" : "err"}:${typeof value}:${value}`; +} +console.log(describe(pick(2)), describe(pick(0))); + +function isOk(r: Result): boolean { + return r[0]; +} +function payload(r: Result): T | E { + return r[1]; +} +console.log(isOk(pick(3)), isOk(pick(-3)), payload(pick(4)), payload(pick(-4))); + +type Triple = readonly [string, number, boolean] | readonly [number, string, boolean]; +const triples: Triple[] = [ + ["a", 1, true], + [2, "b", false], +]; +for (const t of triples) console.log(typeof t[0], typeof t[1], t[2]); + +// A read through a single tuple view of the union. +function flag(r: Result): boolean { + return (r as readonly [boolean, T | E])[0]; +} +console.log(flag(pick(1)), flag(pick(-1))); diff --git a/tests/diagnostics/promise-all-generic-mixed-union.ts b/tests/diagnostics/promise-all-generic-mixed-union.ts new file mode 100644 index 0000000000..8324f6a232 --- /dev/null +++ b/tests/diagnostics/promise-all-generic-mixed-union.ts @@ -0,0 +1,8 @@ +// Promise.all over a generic array instantiated with a union of a value and +// a promise, with an element read of its result: the call is refused by name. +async function f(xs: V[]): Promise { + const values = await Promise.all(xs); + values[0]; +} +f>(["x"]); +export {}; diff --git a/tests/diagnostics/untyped-map-into-interface.ts b/tests/diagnostics/untyped-map-into-interface.ts new file mode 100644 index 0000000000..ba5e26a907 --- /dev/null +++ b/tests/diagnostics/untyped-map-into-interface.ts @@ -0,0 +1,12 @@ +// An untyped `new Map()` satisfies a record interface only through `any`; +// its flow into the interface-typed result is refused at build time, like a +// typed Map is. +interface Cache { + get(key: string): number | undefined; + set(key: string, value: number): unknown; +} +function make(c?: Cache): Cache { + return c ?? new Map(); +} +console.log(make().get("a")); +export {}; diff --git a/tests/harness/__snapshots__/promise-all-generic-mixed-union.ts.txt b/tests/harness/__snapshots__/promise-all-generic-mixed-union.ts.txt new file mode 100644 index 0000000000..b0622d132f --- /dev/null +++ b/tests/harness/__snapshots__/promise-all-generic-mixed-union.ts.txt @@ -0,0 +1,8 @@ +promise-all-generic-mixed-union.ts:4:36 - error SC2020: 'Promise.all over this argument shape' is part of the standard library types but has no scriptc lowering yet (instantiating 'f' with | string>) + + 3 | async function f(xs: V[]): Promise { + 4 | const values = await Promise.all(xs); + | ^~ + 5 | values[0]; + + hint: an array of promises (Promise[]) is the supported form \ No newline at end of file diff --git a/tests/harness/__snapshots__/untyped-map-into-interface.ts.txt b/tests/harness/__snapshots__/untyped-map-into-interface.ts.txt new file mode 100644 index 0000000000..03b54a1368 --- /dev/null +++ b/tests/harness/__snapshots__/untyped-map-into-interface.ts.txt @@ -0,0 +1,8 @@ +untyped-map-into-interface.ts:9:10 - error SC2002: record shapes must match exactly or width-coerce: expected '{ get: (string) => number | undefined; set: (string, number) => unknown }', got 'Map' + + 8 | function make(c?: Cache): Cache { + 9 | return c ?? new Map(); + | ^~~~~~~~~~~~~~ + 10 | } + + hint: width subtyping compiles as a copy (each expected field copied off the source; extra fields drop) — this pair is outside the copy's rules; build a literal with exactly the expected fields instead \ No newline at end of file diff --git a/tests/harness/corpus-costs/fs-readdir-recursive.ts.json b/tests/harness/corpus-costs/fs-readdir-recursive.ts.json new file mode 100644 index 0000000000..ad3232154e --- /dev/null +++ b/tests/harness/corpus-costs/fs-readdir-recursive.ts.json @@ -0,0 +1 @@ +{ "plain": 3521, "san": 40657 } diff --git a/tests/harness/corpus-costs/never-narrowed-literal-argument.ts.json b/tests/harness/corpus-costs/never-narrowed-literal-argument.ts.json new file mode 100644 index 0000000000..1af81d8dfd --- /dev/null +++ b/tests/harness/corpus-costs/never-narrowed-literal-argument.ts.json @@ -0,0 +1 @@ +{ "plain": 2485, "san": 53450 } diff --git a/tests/harness/corpus-costs/rest-tuple-shorthand-property.ts.json b/tests/harness/corpus-costs/rest-tuple-shorthand-property.ts.json new file mode 100644 index 0000000000..0358eaf914 --- /dev/null +++ b/tests/harness/corpus-costs/rest-tuple-shorthand-property.ts.json @@ -0,0 +1 @@ +{ "plain": 2199, "san": 6086 } diff --git a/tests/harness/corpus-costs/string-plus-element-read-return.ts.json b/tests/harness/corpus-costs/string-plus-element-read-return.ts.json new file mode 100644 index 0000000000..17a596f9c7 --- /dev/null +++ b/tests/harness/corpus-costs/string-plus-element-read-return.ts.json @@ -0,0 +1 @@ +{ "plain": 2298, "san": 3533 } diff --git a/tests/harness/corpus-costs/symbol-keyed-recursive-record.ts.json b/tests/harness/corpus-costs/symbol-keyed-recursive-record.ts.json new file mode 100644 index 0000000000..917f53744a --- /dev/null +++ b/tests/harness/corpus-costs/symbol-keyed-recursive-record.ts.json @@ -0,0 +1 @@ +{ "plain": 2702, "san": 43257 } diff --git a/tests/harness/corpus-costs/union-tuple-element-read.ts.json b/tests/harness/corpus-costs/union-tuple-element-read.ts.json new file mode 100644 index 0000000000..fcdd8c1759 --- /dev/null +++ b/tests/harness/corpus-costs/union-tuple-element-read.ts.json @@ -0,0 +1 @@ +{ "plain": 2301, "san": 3138 } From e03afc25f3ae5651d2e33adce51f2bb85d9951d3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 01:51:53 -0500 Subject: [PATCH 46/49] Add range-proof regression cases for constant-choice tables --- tests/corpus/constant-choice-chains.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/corpus/constant-choice-chains.ts b/tests/corpus/constant-choice-chains.ts index 5cbae7d8a2..02757d3e5b 100644 --- a/tests/corpus/constant-choice-chains.ts +++ b/tests/corpus/constant-choice-chains.ts @@ -72,19 +72,34 @@ for (let k = 0; k < 4; k++) { } console.log("short", short.join(",")); -// The index is the compared local, and on the final else's path it is -// past the end, also past 2^32: the store is ignored there. -function indexed(flag: boolean, far: boolean): string { +// The index is the compared local. Inside each arm the range analysis +// proves its value there; on the final else's path it is past the end, also +// past 2^32 and below -2^32, and the store is ignored. +function indexed(flag: boolean): string { const a = new Uint8Array(4); let j = 0; - if (flag) j = far ? 4294967297 : 3; + if (flag) j = 4294967297; if (j === 0) a[j] = 11; else if (j === 1) a[j] = 22; else if (j === 2) a[j] = 33; else a[j] = 44; return a.join(","); } -console.log("indexed", indexed(false, false), indexed(true, false), indexed(true, true)); +function indexedLoop(): string { + const a = new Uint8Array(8); + for (let k = 0; k < 4; k++) { + let j = 1; + if (k === 3) j = 5; + else if (k === 2) j = 4294967298; + else if (k === 1) j = -4294967294; + if (j === 1) a[j] = 11; + else if (j === 2) a[j] = 22; + else if (j === 3) a[j] = 33; + else a[j] = 44; + } + return a.join(","); +} +console.log("indexed", indexed(false), indexed(true), indexedLoop()); // Chains that keep their arms: an arm with a second statement, no final // else, a different index, and a subject that is not a local. From 71de76b81350dfc2b955e10cc400674b8e955cf8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 02:02:18 -0500 Subject: [PATCH 47/49] Order the native compiler's cold functions by how many builds run them --- RELEASING.md | 2 +- packages/compiler/native/cli.layout.gz | Bin 216519 -> 238832 bytes scripts/refresh-cli-layout.mjs | 55 ++++++++++++++++++------- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 42c253568b..8eac23a8ed 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -18,7 +18,7 @@ The release job builds each pinned LLVM helper, precompiled runtime pack, and na Release builds of the native CLI optimize the compiler with a profile collected on the release commit. The `cli-profile` job runs `node scripts/refresh-cli-profile.mjs` on Linux x64, which builds an instrumented compiler, runs it on tsc-ts, a slice of the corpus, and the runtime benchmark workloads, and writes the merged sparse profile. Every platform's CLI build uses that profile (`SCRIPTC_NATIVE_PROFILE_USE`), and the GitHub release attaches it as `scriptc--cli.profdata.gz`. Code the profile does not describe, or describes from older compiler sources, is optimized as without a profile. When the profile job fails, the CLI builds use the committed profile `packages/compiler/native/cli.profdata.gz`, which local and CI builds of the native compiler also use. Refresh the committed profile when compiler code has changed substantially since the last refresh: run `node scripts/refresh-cli-profile.mjs --tsc-ts ` on a quiet Linux x64 host and commit the new profile on its own. -Builds of the native compiler also order its functions by the committed layout `packages/compiler/native/cli.layout.gz`: the functions that `--version` and rebuilds of unchanged programs run come first, then the ones other builds run, so a run maps fewer pages of the executable. Functions the layout does not list keep their usual order. Function names of closures and generated helpers change with nearby edits, so the `cli-profile` job also collects a layout on the release commit (`scripts/refresh-cli-layout.mjs`, reusing the job's instrumented compiler), and the CLI builds use it (`SCRIPTC_NATIVE_LAYOUT`). Refresh the committed layout the same way as the profile, with `node scripts/refresh-cli-layout.mjs`, and commit it on its own. +Builds of the native compiler also order its functions by the committed layout `packages/compiler/native/cli.layout.gz`: the functions that `--version` and rebuilds of unchanged programs run come first, then the ones other builds run, ordered by the share of the layout's training builds that run each, so a run maps fewer pages of the executable. Functions the layout does not list keep their usual order. Function names of closures and generated helpers change with nearby edits, so the `cli-profile` job also collects a layout on the release commit (`scripts/refresh-cli-layout.mjs`, reusing the job's instrumented compiler), and the CLI builds use it (`SCRIPTC_NATIVE_LAYOUT`). Refresh the committed layout the same way as the profile, with `node scripts/refresh-cli-layout.mjs`, and commit it on its own. Publishing uses npm trusted publishing (OIDC) — there is no npm token secret. Each published package must have a GitHub Actions trusted publisher for diff --git a/packages/compiler/native/cli.layout.gz b/packages/compiler/native/cli.layout.gz index ff1f8b97e93e958cd850eb6991d489661b1d678e..4ede69568ab49732d47d475bfa501408a25989ec 100644 GIT binary patch literal 238832 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%0D6d-{@BwR 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 literal 216519 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 diff --git a/scripts/refresh-cli-layout.mjs b/scripts/refresh-cli-layout.mjs index d401ac62b5..cb396015fc 100644 --- a/scripts/refresh-cli-layout.mjs +++ b/scripts/refresh-cli-layout.mjs @@ -1,11 +1,13 @@ #!/usr/bin/env node /* Refreshes packages/compiler/native/cli.layout.gz, the function layout that * builds of the native compiler use (scripts/build-native-cli.mts). The - * layout lists, in two tiers, the functions the compiler runs: tier 1 for - * `--version` and rebuilds of unchanged programs, tier 2 for the other - * builds. The emitter writes the listed functions first, so the code a build + * layout lists the functions the compiler runs, in tiers: tier 1 for + * `--version` and rebuilds of unchanged programs, then the functions of the + * other builds by the share of training builds that run them (COLD_TIERS). + * The emitter writes the listed functions first, by tier, so the code a build * runs sits together in the compiler's executable and a run maps fewer of its - * pages. + * pages: a function nearly every build runs sits with the others that do, not + * among functions only a few programs need. * * Builds an instrumented native compiler for this Linux host (or reuses one * with --instrumented), builds each training program cold and then again @@ -138,9 +140,9 @@ const training = [ // Tier 1 collects each run that took the warm path from its own profile; a // rebuild that missed a cache (it shows no `frontend-cache-hit` or -// `executable-cache-hit` phase) is left out. Tier 2 pools the first builds. +// `executable-cache-hit` phase) is left out. The first builds each write +// their own profile (cold--*), merged across the build's processes. const warm = new Set(); -const cold = new Set(); let skipped = 0; async function warmRun(args, cache, name, cacheHits) { const profile = join(raw, `warm-${name}.profraw`); @@ -159,7 +161,11 @@ const passed = await pool( const cache = join(work, `cache-${index}`); const out = join(builds, `program-${index}`); const args = ["build", program.file, `--optimization=${program.optimization}`, "-o", out]; - const { ok } = await run(args, { cwd: root, cache, profile: join(raw, "cold-%4m.profraw") }); + const { ok } = await run(args, { + cwd: root, + cache, + profile: join(raw, `cold-${index}-%1m.profraw`), + }); if (ok) await warmRun(args, cache, String(index), ["frontend-cache-hit", "executable-cache-hit"]); rmSync(cache, { recursive: true, force: true }); @@ -231,18 +237,39 @@ function executedFunctions(path) { return ran; } -for (const name of readdirSync(raw)) - if (name.startsWith("cold-")) for (const fn of executedFunctions(join(raw, name))) cold.add(fn); +// The cold tiers, by the least share of training builds that run a function: +// tier 2 for functions at least 90% of the builds run, tier 3 for 60%, and so +// on; tier 7 holds the functions fewer than 3% of the builds run. +const COLD_TIERS = [0.9, 0.6, 0.3, 0.1, 0.03]; +const coldRuns = new Map(); +for (const name of readdirSync(raw)) { + const program = /^cold-(\d+)-/.exec(name)?.[1]; + if (program === undefined) continue; + const ran = coldRuns.get(program) ?? new Set(); + for (const fn of executedFunctions(join(raw, name))) ran.add(fn); + coldRuns.set(program, ran); +} +const runs = new Map(); +for (const ran of coldRuns.values()) for (const fn of ran) runs.set(fn, (runs.get(fn) ?? 0) + 1); +const coldTier = (count) => { + const share = count / coldRuns.size; + const index = COLD_TIERS.findIndex((least) => share >= least); + return 2 + (index < 0 ? COLD_TIERS.length : index); +}; const lines = [ ...[...warm].sort().map((name) => `1 ${name}`), - ...[...cold] - .filter((name) => !warm.has(name)) - .sort() - .map((name) => `2 ${name}`), + ...[...runs] + .filter(([name]) => !warm.has(name)) + .map(([name, count]) => [coldTier(count), name]) + .sort((a, b) => a[0] - b[0] || (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0)) + .map(([tier, name]) => `${tier} ${name}`), ]; writeFileSync(output, gzipSync(`${lines.join("\n")}\n`, { level: constants.Z_BEST_COMPRESSION })); +const perTier = new Map(); +for (const line of lines) + perTier.set(line.split(" ")[0], (perTier.get(line.split(" ")[0]) ?? 0) + 1); console.log( - `wrote ${output}: ${warm.size} tier-1 and ${lines.length - warm.size} tier-2 functions (${statSync(output).size} bytes)`, + `wrote ${output}: ${[...perTier].map(([tier, count]) => `tier ${tier}: ${count}`).join(", ")} functions, from ${coldRuns.size} builds (${statSync(output).size} bytes)`, ); rmSync(builds, { recursive: true, force: true }); if (values.work === undefined) rmSync(work, { recursive: true, force: true }); From eaa8a44cdf6a4d4fe8e25ce02442acc4047cbef6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 02:02:46 -0500 Subject: [PATCH 48/49] Read typed-array header fields once at a speculative loop's entry A speculative integer loop's fast copy exits to a copy that resumes the failed iteration, and both read the header fields of the views they access. GVN's load PRE moved the fast copy's read of a view's data pointer ahead of the exit branch, to make the resume copy's read redundant, and put a copy on the exit edge. The loop then kept its first iteration in the loop, so the overflow checks of a value whose range is known after one iteration stayed in every iteration, with a reload of the data pointer. A view held by a const local or a parameter that the loop does not rebind is the same view in every iteration, and its length, data pointer and shared storage never change. The loop's entry now reads them once, and the element accesses of every copy use those values. --- .../src/backend/llvm/bytes-headers.ts | 77 +++++++++++++++++++ .../src/backend/llvm/emit-function.ts | 1 + .../src/backend/llvm/emit-statements.ts | 20 +++++ packages/compiler/src/backend/llvm/emitter.ts | 6 ++ .../compiler/src/backend/llvm/expr-bytes.ts | 24 ++++++ .../test/speculative-loop-emission.test.ts | 44 +++++++++++ .../speculative-loop-view-headers.ts.json | 6 ++ tests/corpus/speculative-loop-view-headers.ts | 36 +++++++++ .../speculative-loop-view-headers.ts.json | 1 + 9 files changed, 215 insertions(+) create mode 100644 packages/compiler/src/backend/llvm/bytes-headers.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-loop-view-headers.ts.json create mode 100644 tests/corpus/speculative-loop-view-headers.ts create mode 100644 tests/harness/corpus-costs/speculative-loop-view-headers.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-headers.ts b/packages/compiler/src/backend/llvm/bytes-headers.ts new file mode 100644 index 0000000000..f5b95cf758 --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-headers.ts @@ -0,0 +1,77 @@ +/* Typed-array header fields read once at a speculative loop's entry. + * + * A view's length, data pointer and shared storage are set at construction + * and never change, and a view held by a const local or a parameter that + * the loop does not rebind is the same view in every iteration. The entry + * reads those fields once, and every element access in the loop's copies + * (the ordinary copy, the fast copy and the copy a failed check resumes in) + * uses the values. No header read is then left inside the copies for LLVM's + * load PRE to move ahead of the fast copy's exits: such a move keeps the + * fast loop's first iteration in the loop, with the checks that one + * iteration of it makes redundant. */ +import type { IrExpr, IrStmt } from "../../ir/ir.js"; +import { everyStmt } from "../../ir/traverse.js"; +import { emitBytesData, emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; +import type { LlEmitter } from "./emitter.js"; + +export interface BytesHeader { + length: string; + data: string; + /** The view's shared storage (worker programs only). */ + shared: string | null; +} + +export interface BytesHeaderScope { + /** The function the scope's values belong to. */ + fn: string; + locals: ReadonlyMap; + /** The header of each receiver value read from one of `locals`. */ + values: Map; +} + +/** Reads the header fields of the views `loop` accesses through a const + * local or a parameter it does not rebind, at the current block, which must + * dominate every copy of the loop. Null when there are none. */ +export function emitLoopBytesHeaders( + emitter: LlEmitter, + loop: IrStmt, + parameters: ReadonlySet, +): BytesHeaderScope | null { + const receivers = new Set(); + const rebound = new Set(); + const receiver = (e: IrExpr): void => { + if (e.kind === "varRef") receivers.add(e.localId); + }; + everyStmt(loop, { + expr: (e) => { + if (e.kind === "bytesIntrinsic") receiver(e.receiver); + else if (e.kind === "assignExpr" || e.kind === "incDec") rebound.add(e.localId); + return true; + }, + stmt: (s) => { + if (s.kind === "bytesSet") receiver(s.arr); + else if (s.kind === "assign" || s.kind === "forOf" || s.kind === "varDecl") + rebound.add(s.localId); + else if (s.kind === "tryCatch" && s.catchLocalId !== null) rebound.add(s.catchLocalId); + return true; + }, + }); + const locals = new Map(); + for (const id of receivers) { + if (rebound.has(id)) continue; + const local = emitter.currentLocals.get(id); + if (local === undefined || local.boxed === true || local.type.kind !== "bytes") continue; + if (local.mutable && !parameters.has(id)) continue; + if (emitter.integerLoopBindings.has(id) || emitter.int32Locals.has(id)) continue; + const B = emitter.B; + const view = B.tmp(); + B.line(`${view} = load ptr, ptr ${emitter.binding(id).slot}${emitter.slotAlias(id)}`); + locals.set(id, { + length: emitBytesHeaderLength(emitter, view), + data: emitBytesData(emitter, view), + shared: emitter.mod.workers ? emitBytesSharedStorage(emitter, view) : null, + }); + } + if (locals.size === 0) return null; + return { fn: emitter.currentFunctionName, locals, values: new Map() }; +} diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index 8c9ace9f31..a901445d16 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -83,6 +83,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { emitter.unwindCleanups.clear(); emitter.jumpTargets = []; emitter.currentLocals = new Map(fn.locals.map((l) => [l.id, l])); + emitter.currentParameters = new Set(fn.params.map((p) => p.localId)); emitter.lazyCaptureBoxes.clear(); emitter.lazyStackBoxes.clear(); // An unchanged captured parameter keeps its value in a plain slot; the diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 66bbf34d0b..85d4742c14 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1,4 +1,6 @@ /* LlEmitter's statement emission: the statement switch, try/catch, switch, loop copies, speculative writes and string self-concatenation. */ +import { emitLoopBytesHeaders } from "./bytes-headers.js"; +import { noteBytesReceiver } from "./expr-bytes.js"; import { type LlEmitter, type LlScopeEntry, @@ -81,6 +83,7 @@ import { planProvenIntegerLoop, planSpeculativeLoop, type ProvenLoopPlan, + type SpeculativeLoopPlan, speculativeReads, speculativeWriteTarget, } from "./speculative-integers.js"; @@ -576,6 +579,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { // IrBytesElem is static, so never rediscover it through the // generic runtime switch in a hot loop. const arr = emitter.emitStableReceiver(s.arr, [s.index, s.value]); + noteBytesReceiver(emitter, s.arr, arr.name); const idx = emitter.emitExpr(s.index); const v = emitter.emitExpr(s.value); if (s.arr.type.kind !== "bytes") @@ -2373,6 +2377,22 @@ export function emitLoopCopies( else ordinary(exit); return; } + const outer = emitter.bytesHeaders; + emitter.bytesHeaders = emitLoopBytesHeaders(emitter, loop, emitter.currentParameters) ?? outer; + try { + emitSpeculativeLoop(emitter, plan, exit, emitCopy); + } finally { + emitter.bytesHeaders = outer; + } +} + +/** The copies of a speculative loop (emitLoopCopies). */ +function emitSpeculativeLoop( + emitter: LlEmitter, + plan: SpeculativeLoopPlan, + exit: string, + emitCopy: (exit: string) => void, +): void { const B = emitter.B; const entries = new Map(); let ok = "true"; diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index e1eed870d0..459dd2d9e1 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -64,6 +64,7 @@ import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; import { findInitializedByteLoopBindings } from "./byte-windows.js"; import { matchBytesLoopVersion } from "./bytes-loop-versions.js"; import { globalSlotAlias } from "./bytes-alias.js"; +import type { BytesHeaderScope } from "./bytes-headers.js"; import type { IntegerCountedForLoop, IntegerPairedForLoop } from "../../ir/integer-loops.js"; import { materializeSplitPiece, @@ -1841,6 +1842,11 @@ export class LlEmitter { * one the element access being emitted carries (bytesElementAlias). */ bytesAliasScopes: ReadonlyMap = new Map(); bytesAccessScope = ""; + /** Header fields a speculative loop's entry read for its views + * (bytes-headers.ts), while its copies are emitted. */ + bytesHeaders: BytesHeaderScope | null = null; + /** The parameters of the function being emitted. */ + currentParameters: ReadonlySet = new Set(); /** Loop versions with alias scopes in the current function; it names * their scopes. */ aliasScopedVersions = 0; diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index 2493a343d1..72ab4ec139 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -1,6 +1,7 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { emitBorrowedInput } from "./borrowed-inputs.js"; +import type { BytesHeader } from "./bytes-headers.js"; import { BYTES_ELEMENT_SIZE, F64, type IrBytesElem, type IrExpr } from "../../ir/ir.js"; import type { LlvmEmitterContext, LlValue } from "./expr-context.js"; import { F64_INF, exactIndexLines, f64Lit, sizeToDouble } from "./common.js"; @@ -160,6 +161,22 @@ export function emitBytesIndex( return idx; } +/** Records that `value` is the view `receiver` reads, so the header reads of + * an access through it use the values a loop entry read (bytes-headers.ts). */ +export function noteBytesReceiver(host: LlvmEmitterContext, receiver: IrExpr, value: string): void { + const scope = host.bytesHeaders; + if (scope === null || receiver.kind !== "varRef" || scope.fn !== host.currentFunctionName) return; + const header = scope.locals.get(receiver.localId); + if (header !== undefined) scope.values.set(value, header); +} + +/** The header fields a loop entry read for the view `receiver`, if any. */ +function cachedHeader(host: LlvmEmitterContext, receiver: string): BytesHeader | undefined { + const scope = host.bytesHeaders; + if (scope === null || scope.fn !== host.currentFunctionName) return undefined; + return scope.values.get(receiver); +} + /** The element count of a typed array, in the target's size type. `value`: * the program reads the count itself (bytesHeaderValueAlias), rather than * only bounding an access. */ @@ -168,6 +185,8 @@ export function emitBytesHeaderLength( receiver: string, value = false, ): string { + const cached = cachedHeader(host, receiver); + if (cached !== undefined) return cached.length; const p = host.B.tmp(); const len = host.B.tmp(); const alias = value ? bytesHeaderValueAlias(host) : bytesHeaderAlias(host); @@ -177,6 +196,8 @@ export function emitBytesHeaderLength( } export function emitBytesData(host: LlvmEmitterContext, receiver: string): string { + const cached = cachedHeader(host, receiver); + if (cached !== undefined) return cached.data; const p = host.B.tmp(); const data = host.B.tmp(); host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 3`); @@ -203,6 +224,8 @@ export function emitBytesLength( /** A view's shared backing (null for a buffer this thread owns). It is * set at construction, before script code can see the view. */ export function emitBytesSharedStorage(host: LlvmEmitterContext, receiver: string): string { + const cached = cachedHeader(host, receiver)?.shared; + if (cached !== undefined && cached !== null) return cached; const B = host.B; const slot = B.tmp(), storage = B.tmp(); @@ -813,6 +836,7 @@ export function emitBytesIntrinsic( const r = directElementAccess ? host.emitStableReceiver(e.receiver, e.args) : emitBorrowedInput(host, e.receiver); + if (directElementAccess) noteBytesReceiver(host, e.receiver, r.name); // Byte operations borrow every input; views and identity results return // independent owners, while writes retain no operand beyond the call. const args = e.args.map((a) => emitBorrowedInput(host, a)); diff --git a/packages/compiler/test/speculative-loop-emission.test.ts b/packages/compiler/test/speculative-loop-emission.test.ts index d26a0c96cd..dcec6d8af1 100644 --- a/packages/compiler/test/speculative-loop-emission.test.ts +++ b/packages/compiler/test/speculative-loop-emission.test.ts @@ -183,3 +183,47 @@ console.log(key(new Uint8Array([1, 2, 3, 0]), 6)); expect(fast).not.toMatch(/ashr i64 %/); expect(fast).not.toContain("@llvm.smul.with.overflow.i64"); }); + +test("a speculative loop reads its views' header fields once, before its copies", async () => { + // A view held by a const local or by a parameter is the same view in + // every iteration. Its data pointer is read before the branch to the fast + // copy, and no copy (ordinary, fast, or the one a failed check resumes + // in) reads it again. + const llvm = await emitLlvm(` +let last = 42; +function fill(array: ArrayBuffer, count: number): void { + const buf = new Float64Array(array); + let l = last; + for (let i = 0; i < count; i++) { + l = (l * 3877 + 29573) % 139968; + buf[i] = l; + } + last = l; +} +function fillInto(buf: Float64Array, count: number): void { + let l = last; + for (let i = 0; i < count; i++) { + l = (l * 3877 + 29573) % 139968; + buf[i] = l; + } + last = l; +} +const array = new ArrayBuffer(8 * 16); +fill(array, 16); +const view = new Float64Array(8); +fillInto(view, 8); +console.log(new Float64Array(array)[15], view[7], last); +`); + const dataReads = /getelementptr inbounds %ScrBytes, ptr %\w+, i64 0, i32 3$/gm; + for (const name of ["fill", "fillInto"]) { + const fn = + new RegExp(`^define internal void @sc_bf_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec( + llvm, + )?.[0] ?? ""; + const branch = /^ {2}br i1 %\w+, label %spec\.fast\d+, label %spec\.slow\d+$/m.exec(fn); + expect(branch, name).not.toBeNull(); + const copies = fn.slice(branch!.index); + expect(copies.match(dataReads), name).toBeNull(); + expect(fn.slice(0, branch!.index).match(dataReads)?.length, name).toBeGreaterThan(0); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-loop-view-headers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-loop-view-headers.ts.json new file mode 100644 index 0000000000..9fcddc195f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-loop-view-headers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/speculative-loop-view-headers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/speculative-loop-view-headers.ts b/tests/corpus/speculative-loop-view-headers.ts new file mode 100644 index 0000000000..a1b92c1d96 --- /dev/null +++ b/tests/corpus/speculative-loop-view-headers.ts @@ -0,0 +1,36 @@ +// Speculative integer loops over typed-array views held by a const local +// and by a parameter. The fast copy, the copy a failed check resumes in and +// the ordinary copy all read and write the same views: a product that +// leaves the safe integers fails the fast copy's check partway through, +// and writes past the end are ignored. +function fill(buffer: ArrayBuffer, count: number, seed: number): number { + const view = new Float64Array(buffer); + let l = seed; + for (let i = 0; i < count; i++) { + l = (l * 3877 + 29573) % 139968; + view[i] = l; + } + return l; +} + +function grow(out: Int32Array, count: number, start: number): number { + let x = start; + for (let i = 0; i < count; i++) { + x = x * 3 + 1; + out[i] = x % 1000; + } + return x; +} + +function sum(view: Float64Array | Int32Array): number { + let s = 0; + for (const v of view) s += v; + return s; +} + +const buffer = new ArrayBuffer(8 * 64); +console.log(fill(buffer, 64, 42), sum(new Float64Array(buffer))); +console.log(fill(buffer, 100, 7), new Float64Array(buffer)[63]); +const out = new Int32Array(40); +console.log(grow(out, 60, 1), sum(out), out[0], out[39]); +console.log(grow(out, 10, 5), out.join(",")); diff --git a/tests/harness/corpus-costs/speculative-loop-view-headers.ts.json b/tests/harness/corpus-costs/speculative-loop-view-headers.ts.json new file mode 100644 index 0000000000..47ead0063c --- /dev/null +++ b/tests/harness/corpus-costs/speculative-loop-view-headers.ts.json @@ -0,0 +1 @@ +{ "plain": 2400 } From 7183a764223d8e4bd5d5dfcb1640ee3baa401b87 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 02:42:04 -0500 Subject: [PATCH 49/49] Address review: missing strings in bindings and calls, evaluated recursive flags - A string held in a binding or returned by a call can be a missing element read (undefined), so `head + PAD[j]` and `at(i) + PAD[j]` still add to NaN when both are missing. Only string literals, templates and sums with such an operand now count as always a string. - `readdirSync(path, { recursive })` folds only the `true` and `false` keywords. Any other flag expression, including one typed as a literal, evaluates at run time for its effects and exceptions. --- .../frontend/lowering/builtins/filesystem.ts | 11 +++++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 19 +++++++++++++------ tests/corpus/fs-readdir-recursive.ts | 16 ++++++++++++++++ .../corpus/string-plus-element-read-return.ts | 15 +++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts index 3b32fa29b3..d8272c7951 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts @@ -1351,9 +1351,16 @@ export function lowerFsReaddirTypesCall( } } if (recursive !== null) { - const flag = lowerer.typeOf(recursive); + // Only the `true`/`false` keywords fold; any other expression, even one + // typed as a literal, evaluates at run time for its effects. + let flag = recursive; + while (ts.isParenthesizedExpression(flag)) flag = flag.expression; const literal = - flag.flags & ts.TypeFlags.BooleanLiteral ? lowerer.checker.typeToString(flag) : null; + flag.kind === ts.SyntaxKind.TrueKeyword + ? "true" + : flag.kind === ts.SyntaxKind.FalseKeyword + ? "false" + : null; if (sawWithFileTypes && literal !== "false") { lowerer.noLowering( `${operation} with withFileTypes and recursive`, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 3f3f6ea336..597ddb9dae 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6011,12 +6011,19 @@ export class Lowerer { if (!primitive(e.left) || !primitive(e.right)) return null; // A missing read is undefined, and `+` yields a number only when // neither operand is a string: an operand that is always a string - // (`"x-" + PAD[i]`) makes the result a string. A read, or a sum or - // call whose result may itself be NaN, is not always a string. - const definitelyString = (part: ts.Expression): boolean => - !stringRead(part) && - this.mapTypeOf(this.typeOf(part))?.kind === "string" && - optionalPrimitiveResultType(part) === null; + // (`"x-" + PAD[i]`) makes the result a string. Only literals, + // templates and sums with such an operand are always strings; a + // binding or call can hold a missing read. + const definitelyString = (part: ts.Expression): boolean => { + const p = peel(part); + if (ts.isStringLiteral(p) || ts.isNoSubstitutionTemplateLiteral(p)) return true; + if (ts.isTemplateExpression(p)) return true; + return ( + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.PlusToken && + (definitelyString(p.left) || definitelyString(p.right)) + ); + }; if (definitelyString(e.left) || definitelyString(e.right)) return null; return { kind: "union", unionId: this.unions.intern([F64, STRING]) }; }; diff --git a/tests/corpus/fs-readdir-recursive.ts b/tests/corpus/fs-readdir-recursive.ts index 310cd3b52e..39fd7f5ccd 100644 --- a/tests/corpus/fs-readdir-recursive.ts +++ b/tests/corpus/fs-readdir-recursive.ts @@ -27,6 +27,22 @@ for (const flag of [false, true]) { } console.log(readdirSync(join(root, "empty"), { recursive: true }).length); +// A flag expression typed `true` or `false` still evaluates, once. +let probes = 0; +function yes(): true { + probes++; + return true; +} +function no(): false { + probes++; + return false; +} +console.log( + readdirSync(join(root, "b"), { recursive: yes() }).length, + readdirSync(join(root, "b"), { recursive: no() }).length, + probes, +); + try { readdirSync(join(root, "missing"), { recursive: true }); } catch (e) { diff --git a/tests/corpus/string-plus-element-read-return.ts b/tests/corpus/string-plus-element-read-return.ts index f4051f6153..ec07fa3d50 100644 --- a/tests/corpus/string-plus-element-read-return.ts +++ b/tests/corpus/string-plus-element-read-return.ts @@ -25,3 +25,18 @@ function pair(i: number, j: number): string { } const both = pair(8, 9); console.log(pair(0, 1), typeof both, String(both)); + +// A missing element held in a binding or returned by a function is undefined, +// so two of them still add to NaN. +function at(i: number): string { + return PAD[i]; +} +function viaBinding(i: number, j: number): string { + const head = PAD[i]; + return head + PAD[j]; +} +function viaCall(i: number, j: number): string { + return at(i) + PAD[j]; +} +console.log(viaBinding(0, 1), String(viaBinding(5, 6)), typeof viaBinding(5, 6)); +console.log(viaCall(0, 2), String(viaCall(5, 6)), viaBinding(0, 9));